From 792fb13ac646dc8c2739e9f56a8fed566f4960f9 Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Sat, 17 Aug 2024 15:16:37 +0200 Subject: [PATCH 001/200] Docs: Clarify rotation syntax of `model` formspec element (#14997) There has been confusion over this in the past, with users wrongly supplying rotation as `{x,y}`. --- doc/lua_api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 389ea73f2..6d486b56b 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -2861,14 +2861,14 @@ Elements * Requires formspec version >= 6. * See `background9[]` documentation for more information. -### `model[,;,;;;;;;;;]` +### `model[,;,;;;;;;;;]` * Show a mesh model. * `name`: Element name that can be used for styling * `mesh`: The mesh model to use. * `textures`: The mesh textures to use according to the mesh materials. Texture names must be separated by commas. -* `rotation {X,Y}` (Optional): Initial rotation of the camera. +* `rotation` (Optional): Initial rotation of the camera, format `x,y`. The axes are euler angles in degrees. * `continuous` (Optional): Whether the rotation is continuous. Default `false`. * `mouse control` (Optional): Whether the model can be controlled with the mouse. Default `true`. From b0ad9a6c3321c58044c5bcb5d2c0d904487d14fa Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sat, 17 Aug 2024 15:49:53 +0100 Subject: [PATCH 002/200] Use JSON file for credits (#14956) --- builtin/mainmenu/credits.json | 85 +++++++++++++++++++++++++ builtin/mainmenu/tab_about.lua | 109 +++++---------------------------- 2 files changed, 99 insertions(+), 95 deletions(-) create mode 100644 builtin/mainmenu/credits.json diff --git a/builtin/mainmenu/credits.json b/builtin/mainmenu/credits.json new file mode 100644 index 000000000..261cf5407 --- /dev/null +++ b/builtin/mainmenu/credits.json @@ -0,0 +1,85 @@ +{ + "#": "https://github.com/orgs/minetest/teams/engine/members", + "core_developers": [ + "Perttu Ahola (celeron55) [Project founder]", + "sfan5 ", + "ShadowNinja ", + "Nathanaëlle Courant (Nore/Ekdohibs) ", + "Loic Blot (nerzhul/nrz) ", + "Andrew Ward (rubenwardy) ", + "Krock/SmallJoker ", + "Lars Hofhansl ", + "v-rob ", + "Desour/DS", + "srifqi", + "Gregor Parzefall (grorp)", + "Lars Müller (luatic)" + ], + "previous_core_developers": [ + "BlockMen", + "Maciej Kasatkin (RealBadAngel) [RIP]", + "Lisa Milne (darkrose) ", + "proller", + "Ilya Zhuravlev (xyz) ", + "PilzAdam ", + "est31 ", + "kahrl ", + "Ryan Kwolek (kwolekr) ", + "sapier", + "Zeno", + "Auke Kok (sofar) ", + "Aaron Suen ", + "paramat", + "Pierre-Yves Rollo ", + "hecks", + "Jude Melton-Houghton (TurkeyMcMac) [RIP]", + "Hugues Ross ", + "Dmitry Kostenko (x2048) " + ], + "#": "Currently only https://github.com/orgs/minetest/teams/triagers/members", + "core_team": [ + "Zughy [Issue triager]", + "wsor [Issue triager]", + "Hugo Locurcio (Calinou) [Issue triager]" + ], + "#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py", + "contributors": [ + "cx384", + "numzero", + "AFCMS", + "sfence", + "Wuzzy", + "ROllerozxa", + "JosiahWI", + "OgelGames", + "David Heidelberg", + "1F616EMO", + "HybridDog", + "Bradley Pierce (Thresher)", + "savilli", + "Stvk imension", + "y5nw", + "chmodsayshello", + "jordan4ibanez", + "superfloh247" + ], + "previous_contributors": [ + "Nils Dagsson Moskopp (erlehmann) [Minetest logo]", + "red-001 ", + "Giuseppe Bilotta", + "HybridDog", + "ClobberXD", + "Dániel Juhász (juhdanad) ", + "MirceaKitsune ", + "Jean-Patrick Guerrero (kilbith)", + "MoNTE48", + "Constantin Wenger (SpeedProg)", + "Ciaran Gultnieks (CiaranG)", + "Paul Ouellette (pauloue)", + "stujones11", + "Rogier ", + "Gregory Currie (gregorycu)", + "JacobF", + "Jeija " + ] +} diff --git a/builtin/mainmenu/tab_about.lua b/builtin/mainmenu/tab_about.lua index d798b5b09..0394ea507 100644 --- a/builtin/mainmenu/tab_about.lua +++ b/builtin/mainmenu/tab_about.lua @@ -15,96 +15,6 @@ --with this program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. --- https://github.com/orgs/minetest/teams/engine/members - -local core_developers = { - "Perttu Ahola (celeron55) [Project founder]", - "sfan5 ", - "ShadowNinja ", - "Nathanaëlle Courant (Nore/Ekdohibs) ", - "Loic Blot (nerzhul/nrz) ", - "Andrew Ward (rubenwardy) ", - "Krock/SmallJoker ", - "Lars Hofhansl ", - "v-rob ", - "Desour/DS", - "srifqi", - "Gregor Parzefall (grorp)", - "Lars Müller (luatic)", -} - --- currently only https://github.com/orgs/minetest/teams/triagers/members - -local core_team = { - "Zughy [Issue triager]", - "wsor [Issue triager]", - "Hugo Locurcio (Calinou) [Issue triager]", -} - --- For updating active/previous contributors, see the script in ./util/gather_git_credits.py - -local active_contributors = { - "cx384", - "numzero", - "AFCMS", - "sfence", - "Wuzzy", - "ROllerozxa", - "JosiahWI", - "OgelGames", - "David Heidelberg", - "1F616EMO", - "HybridDog", - "Bradley Pierce (Thresher)", - "savilli", - "Stvk imension", - "y5nw", - "chmodsayshello", - "jordan4ibanez", - "superfloh247", -} - -local previous_core_developers = { - "BlockMen", - "Maciej Kasatkin (RealBadAngel) [RIP]", - "Lisa Milne (darkrose) ", - "proller", - "Ilya Zhuravlev (xyz) ", - "PilzAdam ", - "est31 ", - "kahrl ", - "Ryan Kwolek (kwolekr) ", - "sapier", - "Zeno", - "Auke Kok (sofar) ", - "Aaron Suen ", - "paramat", - "Pierre-Yves Rollo ", - "hecks", - "Jude Melton-Houghton (TurkeyMcMac) [RIP]", - "Hugues Ross ", - "Dmitry Kostenko (x2048) ", -} - -local previous_contributors = { - "Nils Dagsson Moskopp (erlehmann) [Minetest logo]", - "red-001 ", - "Giuseppe Bilotta", - "HybridDog", - "ClobberXD", - "Dániel Juhász (juhdanad) ", - "MirceaKitsune ", - "Jean-Patrick Guerrero (kilbith)", - "MoNTE48", - "Constantin Wenger (SpeedProg)", - "Ciaran Gultnieks (CiaranG)", - "Paul Ouellette (pauloue)", - "stujones11", - "Rogier ", - "Gregory Currie (gregorycu)", - "JacobF", - "Jeija ", -} local function prepare_credits(dest, source) local string = table.concat(source, "\n") .. "\n" @@ -120,6 +30,13 @@ local function prepare_credits(dest, source) table.insert(dest, string) end +local function get_credits() + local f = assert(io.open(core.get_mainmenu_path() .. "/credits.json")) + local json = core.parse_json(f:read("*all")) + f:close() + return json +end + return { name = "about", caption = fgettext("About"), @@ -133,30 +50,32 @@ return { "", } + local credits = get_credits() + table.insert_all(hypertext, { "", fgettext_ne("Core Developers"), "\n", }) - prepare_credits(hypertext, core_developers) + prepare_credits(hypertext, credits.core_developers) table.insert_all(hypertext, { "\n", "", fgettext_ne("Core Team"), "\n", }) - prepare_credits(hypertext, core_team) + prepare_credits(hypertext, credits.core_team) table.insert_all(hypertext, { "\n", "", fgettext_ne("Active Contributors"), "\n", }) - prepare_credits(hypertext, active_contributors) + prepare_credits(hypertext, credits.contributors) table.insert_all(hypertext, { "\n", "", fgettext_ne("Previous Core Developers"), "\n", }) - prepare_credits(hypertext, previous_core_developers) + prepare_credits(hypertext, credits.previous_core_developers) table.insert_all(hypertext, { "\n", "", fgettext_ne("Previous Contributors"), "\n", }) - prepare_credits(hypertext, previous_contributors) + prepare_credits(hypertext, credits.previous_contributors) hypertext = table.concat(hypertext):sub(1, -2) From 1fb49e9ca7c66251503be47608243f124061bf44 Mon Sep 17 00:00:00 2001 From: j-r Date: Sat, 17 Aug 2024 19:48:40 +0200 Subject: [PATCH 003/200] Add shared mods path to get_modpaths ...because the documentation implies it should be. --- src/script/lua_api/l_mainmenu.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index a5913e807..97101955a 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -675,6 +675,11 @@ int ModApiMainMenu::l_get_modpaths(lua_State *L) ModApiMainMenu::l_get_modpath(L); lua_setfield(L, -2, "mods"); + std::string modpath = fs::RemoveRelativePathComponents( + porting::path_share + DIR_DELIM + "mods" + DIR_DELIM); + lua_pushstring(L, modpath.c_str()); + lua_setfield(L, -2, "share"); + for (const std::string &component : getEnvModPaths()) { lua_pushstring(L, component.c_str()); lua_setfield(L, -2, fs::AbsolutePath(component).c_str()); From 5acc2736db4583a4e7c23484ef3d89d4bf931b7b Mon Sep 17 00:00:00 2001 From: 1F616EMO~nya Date: Sun, 18 Aug 2024 01:48:54 +0800 Subject: [PATCH 004/200] Translate access denied strings (#14842) --- src/network/clientpackethandler.cpp | 22 +++++++++++++++++++--- src/network/networkprotocol.h | 16 ---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index e2bcb51b5..4b9e8ceea 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -48,6 +48,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "particles.h" #include +const char *accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { + N_("Invalid password"), + N_("Your client sent something the server didn't expect. Try reconnecting or updating your client."), + N_("The server is running in singleplayer mode. You cannot connect."), + N_("Your client's version is not supported.\nPlease contact the server administrator."), + N_("Player name contains disallowed characters"), + N_("Player name not allowed"), + N_("Too many users"), + N_("Empty passwords are disallowed. Set a password and try again."), + N_("Another client is connected with this name. If your client closed unexpectedly, try again in a minute."), + N_("Internal server error"), + "", + N_("Server shutting down"), + N_("The server has experienced an internal error. You will now be disconnected.") +}; + void Client::handleCommand_Deprecated(NetworkPacket* pkt) { infostream << "Got deprecated command " @@ -216,17 +232,17 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) denyCode == SERVER_ACCESSDENIED_CRASH) { *pkt >> m_access_denied_reason; if (m_access_denied_reason.empty()) - m_access_denied_reason = accessDeniedStrings[denyCode]; + m_access_denied_reason = gettext(accessDeniedStrings[denyCode]); u8 reconnect; *pkt >> reconnect; m_access_denied_reconnect = reconnect & 1; } else if (denyCode == SERVER_ACCESSDENIED_CUSTOM_STRING) { *pkt >> m_access_denied_reason; } else if (denyCode == SERVER_ACCESSDENIED_TOO_MANY_USERS) { - m_access_denied_reason = accessDeniedStrings[denyCode]; + m_access_denied_reason = gettext(accessDeniedStrings[denyCode]); m_access_denied_reconnect = true; } else if (denyCode < SERVER_ACCESSDENIED_MAX) { - m_access_denied_reason = accessDeniedStrings[denyCode]; + m_access_denied_reason = gettext(accessDeniedStrings[denyCode]); } else { // Allow us to add new error messages to the // protocol without raising the protocol version, if we want to. diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index e3618042f..d84c735b8 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -1150,22 +1150,6 @@ enum NetProtoCompressionMode { NETPROTO_COMPRESSION_NONE = 0, }; -constexpr const char *accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { - "Invalid password", - "Your client sent something the server didn't expect. Try reconnecting or updating your client.", - "The server is running in simple singleplayer mode. You cannot connect.", - "Your client's version is not supported.\nPlease contact the server administrator.", - "Player name contains disallowed characters", - "Player name not allowed", - "Too many users", - "Empty passwords are disallowed. Set a password and try again.", - "Another client is connected with this name. If your client closed unexpectedly, try again in a minute.", - "Internal server error", - "", - "Server shutting down", - "The server has experienced an internal error. You will now be disconnected." -}; - enum PlayerListModifer : u8 { PLAYER_LIST_INIT, From 5d226268df764c36fc4d550c3c0bc980c7e34f0d Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 17 Aug 2024 19:49:11 +0200 Subject: [PATCH 005/200] Irrlicht cleanups (mostly getting rid of `core::array`) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> --- irr/include/CMeshBuffer.h | 40 ++--- irr/include/IAttributes.h | 35 ---- irr/include/IFileSystem.h | 107 ----------- irr/include/ISceneNode.h | 1 + irr/include/IVideoDriver.h | 21 --- irr/include/SAnimatedMesh.h | 10 +- irr/include/SMaterial.h | 1 - irr/include/SMesh.h | 35 ++-- irr/include/SOverrideMaterial.h | 8 +- irr/include/SSkinMeshBuffer.h | 80 +++++---- irr/include/irrArray.h | 17 -- irr/src/CAttributeImpl.h | 3 - irr/src/CAttributes.cpp | 191 ++++---------------- irr/src/CAttributes.h | 65 ++----- irr/src/CB3DMeshFileLoader.cpp | 2 +- irr/src/CBillboardSceneNode.cpp | 6 +- irr/src/CFileSystem.cpp | 269 +--------------------------- irr/src/CFileSystem.h | 40 +---- irr/src/CMeshManipulator.cpp | 42 ++--- irr/src/CNullDriver.cpp | 33 +--- irr/src/CNullDriver.h | 17 +- irr/src/COGLESDriver.cpp | 57 +----- irr/src/COGLESDriver.h | 12 +- irr/src/COGLESExtensionHandler.cpp | 7 +- irr/src/COGLESExtensionHandler.h | 1 - irr/src/COpenGLCoreTexture.h | 36 ++-- irr/src/COpenGLDriver.cpp | 55 +----- irr/src/COpenGLDriver.h | 26 +-- irr/src/COpenGLExtensionHandler.cpp | 4 +- irr/src/COpenGLExtensionHandler.h | 2 - irr/src/CSceneManager.cpp | 89 +++++---- irr/src/CSceneManager.h | 16 +- irr/src/CXMeshFileLoader.cpp | 10 +- irr/src/CZipReader.cpp | 9 +- irr/src/CZipReader.h | 4 +- irr/src/IAttribute.h | 20 +-- irr/src/OpenGL/Driver.cpp | 65 ++----- irr/src/OpenGL/Driver.h | 23 +-- irr/src/OpenGL/MaterialRenderer.cpp | 19 +- irr/src/OpenGL/MaterialRenderer.h | 8 +- src/client/clouds.cpp | 8 +- src/client/hud.cpp | 31 ++-- src/client/mapblock_mesh.cpp | 2 +- src/client/minimap.cpp | 4 +- src/client/sky.cpp | 4 +- 45 files changed, 308 insertions(+), 1227 deletions(-) diff --git a/irr/include/CMeshBuffer.h b/irr/include/CMeshBuffer.h index 8f8158ff1..0b0c3bb92 100644 --- a/irr/include/CMeshBuffer.h +++ b/irr/include/CMeshBuffer.h @@ -4,7 +4,7 @@ #pragma once -#include "irrArray.h" +#include #include "IMeshBuffer.h" namespace irr @@ -43,21 +43,21 @@ public: /** \return Pointer to vertices. */ const void *getVertices() const override { - return Vertices.const_pointer(); + return Vertices.data(); } //! Get pointer to vertices /** \return Pointer to vertices. */ void *getVertices() override { - return Vertices.pointer(); + return Vertices.data(); } //! Get number of vertices /** \return Number of vertices. */ u32 getVertexCount() const override { - return Vertices.size(); + return static_cast(Vertices.size()); } //! Get type of index data which is stored in this meshbuffer. @@ -71,21 +71,21 @@ public: /** \return Pointer to indices. */ const u16 *getIndices() const override { - return Indices.const_pointer(); + return Indices.data(); } //! Get pointer to indices /** \return Pointer to indices. */ u16 *getIndices() override { - return Indices.pointer(); + return Indices.data(); } //! Get number of indices /** \return Number of indices. */ u32 getIndexCount() const override { - return Indices.size(); + return static_cast(Indices.size()); } //! Get the axis aligned bounding box @@ -160,27 +160,23 @@ public: } //! Append the vertices and indices to the current buffer - /** Only works for compatible types, i.e. either the same type - or the main buffer is of standard type. Otherwise, behavior is - undefined. - */ void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) override { if (vertices == getVertices()) return; const u32 vertexCount = getVertexCount(); - u32 i; + const u32 indexCount = getIndexCount(); - Vertices.reallocate(vertexCount + numVertices); - for (i = 0; i < numVertices; ++i) { - Vertices.push_back(static_cast(vertices)[i]); - BoundingBox.addInternalPoint(static_cast(vertices)[i].Pos); - } + auto *vt = static_cast(vertices); + Vertices.insert(Vertices.end(), vt, vt + numVertices); + for (u32 i = vertexCount; i < getVertexCount(); i++) + BoundingBox.addInternalPoint(Vertices[i].Pos); - Indices.reallocate(getIndexCount() + numIndices); - for (i = 0; i < numIndices; ++i) { - Indices.push_back(indices[i] + vertexCount); + Indices.insert(Indices.end(), indices, indices + numIndices); + if (vertexCount != 0) { + for (u32 i = indexCount; i < getIndexCount(); i++) + Indices[i] += vertexCount; } } @@ -255,9 +251,9 @@ public: //! Material for this meshbuffer. video::SMaterial Material; //! Vertices of this buffer - core::array Vertices; + std::vector Vertices; //! Indices into the vertices of this buffer. - core::array Indices; + std::vector Indices; //! Bounding box of this meshbuffer. core::aabbox3d BoundingBox; //! Primitive type used for rendering (triangles, lines, ...) diff --git a/irr/include/IAttributes.h b/irr/include/IAttributes.h index 906d334a2..4606c5710 100644 --- a/irr/include/IAttributes.h +++ b/irr/include/IAttributes.h @@ -23,27 +23,13 @@ namespace io class IAttributes : public virtual IReferenceCounted { public: - //! Returns amount of attributes in this collection of attributes. - virtual u32 getAttributeCount() const = 0; - - //! Returns attribute name by index. - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - virtual const c8 *getAttributeName(s32 index) const = 0; - //! Returns the type of an attribute //! \param attributeName: Name for the attribute virtual E_ATTRIBUTE_TYPE getAttributeType(const c8 *attributeName) const = 0; - //! Returns attribute type by index. - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - virtual E_ATTRIBUTE_TYPE getAttributeType(s32 index) const = 0; - //! Returns if an attribute with a name exists virtual bool existsAttribute(const c8 *attributeName) const = 0; - //! Returns attribute index from name, -1 if not found - virtual s32 findAttribute(const c8 *attributeName) const = 0; - //! Removes all attributes virtual void clear() = 0; @@ -65,13 +51,6 @@ public: //! \return Returns value of the attribute previously set by setAttribute() virtual s32 getAttributeAsInt(const c8 *attributeName, irr::s32 defaultNotFound = 0) const = 0; - //! Gets an attribute as integer value - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - virtual s32 getAttributeAsInt(s32 index) const = 0; - - //! Sets an attribute as integer value - virtual void setAttribute(s32 index, s32 value) = 0; - /* Float Attribute @@ -90,13 +69,6 @@ public: //! \return Returns value of the attribute previously set by setAttribute() virtual f32 getAttributeAsFloat(const c8 *attributeName, irr::f32 defaultNotFound = 0.f) const = 0; - //! Gets an attribute as float value - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - virtual f32 getAttributeAsFloat(s32 index) const = 0; - - //! Sets an attribute as float value - virtual void setAttribute(s32 index, f32 value) = 0; - /* Bool Attribute */ @@ -112,13 +84,6 @@ public: //! \param defaultNotFound Value returned when attributeName was not found //! \return Returns value of the attribute previously set by setAttribute() virtual bool getAttributeAsBool(const c8 *attributeName, bool defaultNotFound = false) const = 0; - - //! Gets an attribute as boolean value - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - virtual bool getAttributeAsBool(s32 index) const = 0; - - //! Sets an attribute as boolean value - virtual void setAttribute(s32 index, bool value) = 0; }; } // end namespace io diff --git a/irr/include/IFileSystem.h b/irr/include/IFileSystem.h index 1fe9fe6f2..f144bbaee 100644 --- a/irr/include/IFileSystem.h +++ b/irr/include/IFileSystem.h @@ -85,113 +85,6 @@ public: See IReferenceCounted::drop() for more information. */ virtual IWriteFile *createAndWriteFile(const path &filename, bool append = false) = 0; - //! Adds an archive to the file system. - /** After calling this, the Irrlicht Engine will also search and open - files directly from this archive. This is useful for hiding data from - the end user, speeding up file access and making it possible to access - for example Quake3 .pk3 files, which are just renamed .zip files. By - default Irrlicht supports ZIP, PAK, TAR, PNK, and directories as - archives. You can provide your own archive types by implementing - IArchiveLoader and passing an instance to addArchiveLoader. - Irrlicht supports AES-encrypted zip files, and the advanced compression - techniques lzma and bzip2. - \param filename: Filename of the archive to add to the file system. - \param ignoreCase: If set to true, files in the archive can be accessed without - writing all letters in the right case. - \param ignorePaths: If set to true, files in the added archive can be accessed - without its complete path. - \param archiveType: If no specific E_FILE_ARCHIVE_TYPE is selected then - the type of archive will depend on the extension of the file name. If - you use a different extension then you can use this parameter to force - a specific type of archive. - \param password An optional password, which is used in case of encrypted archives. - \param retArchive A pointer that will be set to the archive that is added. - \return True if the archive was added successfully, false if not. */ - virtual bool addFileArchive(const path &filename, bool ignoreCase = true, - bool ignorePaths = true, - E_FILE_ARCHIVE_TYPE archiveType = EFAT_UNKNOWN, - const core::stringc &password = "", - IFileArchive **retArchive = 0) = 0; - - //! Adds an archive to the file system. - /** After calling this, the Irrlicht Engine will also search and open - files directly from this archive. This is useful for hiding data from - the end user, speeding up file access and making it possible to access - for example Quake3 .pk3 files, which are just renamed .zip files. By - default Irrlicht supports ZIP, PAK, TAR, PNK, and directories as - archives. You can provide your own archive types by implementing - IArchiveLoader and passing an instance to addArchiveLoader. - Irrlicht supports AES-encrypted zip files, and the advanced compression - techniques lzma and bzip2. - If you want to add a directory as an archive, prefix its name with a - slash in order to let Irrlicht recognize it as a folder mount (mypath/). - Using this technique one can build up a search order, because archives - are read first, and can be used more easily with relative filenames. - \param file: Archive to add to the file system. - \param ignoreCase: If set to true, files in the archive can be accessed without - writing all letters in the right case. - \param ignorePaths: If set to true, files in the added archive can be accessed - without its complete path. - \param archiveType: If no specific E_FILE_ARCHIVE_TYPE is selected then - the type of archive will depend on the extension of the file name. If - you use a different extension then you can use this parameter to force - a specific type of archive. - \param password An optional password, which is used in case of encrypted archives. - \param retArchive A pointer that will be set to the archive that is added. - \return True if the archive was added successfully, false if not. */ - virtual bool addFileArchive(IReadFile *file, bool ignoreCase = true, - bool ignorePaths = true, - E_FILE_ARCHIVE_TYPE archiveType = EFAT_UNKNOWN, - const core::stringc &password = "", - IFileArchive **retArchive = 0) = 0; - - //! Adds an archive to the file system. - /** \param archive: The archive to add to the file system. - \return True if the archive was added successfully, false if not. */ - virtual bool addFileArchive(IFileArchive *archive) = 0; - - //! Get the number of archives currently attached to the file system - virtual u32 getFileArchiveCount() const = 0; - - //! Removes an archive from the file system. - /** This will close the archive and free any file handles, but will not - close resources which have already been loaded and are now cached, for - example textures and meshes. - \param index: The index of the archive to remove - \return True on success, false on failure */ - virtual bool removeFileArchive(u32 index) = 0; - - //! Removes an archive from the file system. - /** This will close the archive and free any file handles, but will not - close resources which have already been loaded and are now cached, for - example textures and meshes. Note that a relative filename might be - interpreted differently on each call, depending on the current working - directory. In case you want to remove an archive that was added using - a relative path name, you have to change to the same working directory - again. This means, that the filename given on creation is not an - identifier for the archive, but just a usual filename that is used for - locating the archive to work with. - \param filename The archive pointed to by the name will be removed - \return True on success, false on failure */ - virtual bool removeFileArchive(const path &filename) = 0; - - //! Removes an archive from the file system. - /** This will close the archive and free any file handles, but will not - close resources which have already been loaded and are now cached, for - example textures and meshes. - \param archive The archive to remove. - \return True on success, false on failure */ - virtual bool removeFileArchive(const IFileArchive *archive) = 0; - - //! Changes the search order of attached archives. - /** - \param sourceIndex: The index of the archive to change - \param relative: The relative change in position, archives with a lower index are searched first */ - virtual bool moveFileArchive(u32 sourceIndex, s32 relative) = 0; - - //! Get the archive at a given index. - virtual IFileArchive *getFileArchive(u32 index) = 0; - //! Adds an external archive loader to the engine. /** Use this function to add support for new archive types to the engine, for example proprietary or encrypted file storage. */ diff --git a/irr/include/ISceneNode.h b/irr/include/ISceneNode.h index 7a03a2256..1eab3a3fd 100644 --- a/irr/include/ISceneNode.h +++ b/irr/include/ISceneNode.h @@ -10,6 +10,7 @@ #include "EDebugSceneTypes.h" #include "SMaterial.h" #include "irrString.h" +#include "irrArray.h" #include "aabbox3d.h" #include "matrix4.h" #include "IAttributes.h" diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index 2d651c6bf..888e4a9f7 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -182,7 +182,6 @@ public: MaxSupportedTextures (int) The maximum number of simultaneous textures supported by the fixed function pipeline of the (hw) driver. The actual supported number of textures supported by the engine can be lower. MaxLights (int) Number of hardware lights supported in the fixed function pipeline of the driver, typically 6-8. Use light manager or deferred shading for more. MaxAnisotropy (int) Number of anisotropy levels supported for filtering. At least 1, max is typically at 16 or 32. - MaxUserClipPlanes (int) Number of additional clip planes, which can be set by the user via dedicated driver methods. MaxAuxBuffers (int) Special render buffers, which are currently not really usable inside Irrlicht. Only supported by OpenGL MaxMultipleRenderTargets (int) Number of render targets which can be bound simultaneously. Rendering to MRTs is done via shaders. MaxIndices (int) Number of indices which can be used in one render call (i.e. one mesh buffer). @@ -1109,26 +1108,6 @@ public: \return Pointer to loaded texture, or 0 if not found. */ virtual video::ITexture *findTexture(const io::path &filename) = 0; - //! Set or unset a clipping plane. - /** There are at least 6 clipping planes available for the user - to set at will. - \param index The plane index. Must be between 0 and - MaxUserClipPlanes. - \param plane The plane itself. - \param enable If true, enable the clipping plane else disable - it. - \return True if the clipping plane is usable. */ - virtual bool setClipPlane(u32 index, const core::plane3df &plane, bool enable = false) = 0; - - //! Enable or disable a clipping plane. - /** There are at least 6 clipping planes available for the user - to set at will. - \param index The plane index. Must be between 0 and - MaxUserClipPlanes. - \param enable If true, enable the clipping plane else disable - it. */ - virtual void enableClipPlane(u32 index, bool enable) = 0; - //! Set the minimum number of vertices for which a hw buffer will be created /** \param count Number of vertices to set as minimum. */ virtual void setMinHardwareBufferVertexCount(u32 count) = 0; diff --git a/irr/include/SAnimatedMesh.h b/irr/include/SAnimatedMesh.h index 8fdaae0ee..42ba6b952 100644 --- a/irr/include/SAnimatedMesh.h +++ b/irr/include/SAnimatedMesh.h @@ -4,10 +4,10 @@ #pragma once +#include #include "IAnimatedMesh.h" #include "IMesh.h" #include "aabbox3d.h" -#include "irrArray.h" namespace irr { @@ -32,15 +32,15 @@ struct SAnimatedMesh : public IAnimatedMesh virtual ~SAnimatedMesh() { // drop meshes - for (u32 i = 0; i < Meshes.size(); ++i) - Meshes[i]->drop(); + for (auto *mesh : Meshes) + mesh->drop(); } //! Gets the frame count of the animated mesh. /** \return Amount of frames. If the amount is 1, it is a static, non animated mesh. */ u32 getFrameCount() const override { - return Meshes.size(); + return static_cast(Meshes.size()); } //! Gets the default animation speed of the animated mesh. @@ -161,7 +161,7 @@ struct SAnimatedMesh : public IAnimatedMesh } //! All meshes defining the animated mesh - core::array Meshes; + std::vector Meshes; //! The bounding box of this mesh core::aabbox3d Box; diff --git a/irr/include/SMaterial.h b/irr/include/SMaterial.h index c803f5fde..8f24b9984 100644 --- a/irr/include/SMaterial.h +++ b/irr/include/SMaterial.h @@ -6,7 +6,6 @@ #include "SColor.h" #include "matrix4.h" -#include "irrArray.h" #include "irrMath.h" #include "EMaterialTypes.h" #include "EMaterialProps.h" diff --git a/irr/include/SMesh.h b/irr/include/SMesh.h index e865a5d2d..a391255a1 100644 --- a/irr/include/SMesh.h +++ b/irr/include/SMesh.h @@ -4,10 +4,10 @@ #pragma once +#include #include "IMesh.h" #include "IMeshBuffer.h" #include "aabbox3d.h" -#include "irrArray.h" namespace irr { @@ -28,15 +28,15 @@ struct SMesh : public IMesh virtual ~SMesh() { // drop buffers - for (u32 i = 0; i < MeshBuffers.size(); ++i) - MeshBuffers[i]->drop(); + for (auto *buf : MeshBuffers) + buf->drop(); } //! clean mesh virtual void clear() { - for (u32 i = 0; i < MeshBuffers.size(); ++i) - MeshBuffers[i]->drop(); + for (auto *buf : MeshBuffers) + buf->drop(); MeshBuffers.clear(); BoundingBox.reset(0.f, 0.f, 0.f); } @@ -44,7 +44,7 @@ struct SMesh : public IMesh //! returns amount of mesh buffers. u32 getMeshBufferCount() const override { - return MeshBuffers.size(); + return static_cast(MeshBuffers.size()); } //! returns pointer to a mesh buffer @@ -57,12 +57,11 @@ struct SMesh : public IMesh /** reverse search */ IMeshBuffer *getMeshBuffer(const video::SMaterial &material) const override { - for (s32 i = (s32)MeshBuffers.size() - 1; i >= 0; --i) { - if (material == MeshBuffers[i]->getMaterial()) - return MeshBuffers[i]; + for (auto it = MeshBuffers.rbegin(); it != MeshBuffers.rend(); it++) { + if (material == (*it)->getMaterial()) + return *it; } - - return 0; + return nullptr; } //! returns an axis aligned bounding box @@ -81,8 +80,8 @@ struct SMesh : public IMesh void recalculateBoundingBox() { bool hasMeshBufferBBox = false; - for (u32 i = 0; i < MeshBuffers.size(); ++i) { - const core::aabbox3df &bb = MeshBuffers[i]->getBoundingBox(); + for (auto *buf : MeshBuffers) { + const core::aabbox3df &bb = buf->getBoundingBox(); if (!bb.isEmpty()) { if (!hasMeshBufferBBox) { hasMeshBufferBBox = true; @@ -110,19 +109,19 @@ struct SMesh : public IMesh //! set the hardware mapping hint, for driver void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override { - for (u32 i = 0; i < MeshBuffers.size(); ++i) - MeshBuffers[i]->setHardwareMappingHint(newMappingHint, buffer); + for (auto *buf : MeshBuffers) + buf->setHardwareMappingHint(newMappingHint, buffer); } //! flags the meshbuffer as changed, reloads hardware buffers void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override { - for (u32 i = 0; i < MeshBuffers.size(); ++i) - MeshBuffers[i]->setDirty(buffer); + for (auto *buf : MeshBuffers) + buf->setDirty(buffer); } //! The meshbuffers of this mesh - core::array MeshBuffers; + std::vector MeshBuffers; //! The bounding box of this mesh core::aabbox3d BoundingBox; diff --git a/irr/include/SOverrideMaterial.h b/irr/include/SOverrideMaterial.h index 6de6e6ebb..c52a55b55 100644 --- a/irr/include/SOverrideMaterial.h +++ b/irr/include/SOverrideMaterial.h @@ -4,6 +4,7 @@ #pragma once +#include #include "SMaterial.h" namespace irr @@ -57,7 +58,7 @@ struct SOverrideMaterial }; //! To overwrite SMaterial::MaterialType - core::array MaterialTypes; + std::vector MaterialTypes; //! Default constructor SOverrideMaterial() : @@ -83,9 +84,8 @@ struct SOverrideMaterial void apply(SMaterial &material) { if (Enabled) { - for (u32 i = 0; i < MaterialTypes.size(); ++i) { - const SMaterialTypeReplacement &mtr = MaterialTypes[i]; - if (mtr.Original < 0 || (s32)mtr.Original == material.MaterialType) + for (const auto &mtr : MaterialTypes) { + if (mtr.Original < 0 || mtr.Original == (s32)material.MaterialType) material.MaterialType = (E_MATERIAL_TYPE)mtr.Replacement; } for (u32 f = 0; f < 32; ++f) { diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index 5ced6057d..fe9d78321 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -18,9 +18,8 @@ struct SSkinMeshBuffer : public IMeshBuffer //! Default constructor SSkinMeshBuffer(video::E_VERTEX_TYPE vt = video::EVT_STANDARD) : ChangedID_Vertex(1), ChangedID_Index(1), VertexType(vt), - PrimitiveType(EPT_TRIANGLES), + PrimitiveType(EPT_TRIANGLES), HWBuffer(nullptr), MappingHint_Vertex(EHM_NEVER), MappingHint_Index(EHM_NEVER), - HWBuffer(NULL), BoundingBoxNeedsRecalculated(true) { #ifdef _DEBUG @@ -58,11 +57,11 @@ struct SSkinMeshBuffer : public IMeshBuffer { switch (VertexType) { case video::EVT_2TCOORDS: - return Vertices_2TCoords.const_pointer(); + return Vertices_2TCoords.data(); case video::EVT_TANGENTS: - return Vertices_Tangents.const_pointer(); + return Vertices_Tangents.data(); default: - return Vertices_Standard.const_pointer(); + return Vertices_Standard.data(); } } @@ -71,11 +70,11 @@ struct SSkinMeshBuffer : public IMeshBuffer { switch (VertexType) { case video::EVT_2TCOORDS: - return Vertices_2TCoords.pointer(); + return Vertices_2TCoords.data(); case video::EVT_TANGENTS: - return Vertices_Tangents.pointer(); + return Vertices_Tangents.data(); default: - return Vertices_Standard.pointer(); + return Vertices_Standard.data(); } } @@ -84,11 +83,11 @@ struct SSkinMeshBuffer : public IMeshBuffer { switch (VertexType) { case video::EVT_2TCOORDS: - return Vertices_2TCoords.size(); + return static_cast(Vertices_2TCoords.size()); case video::EVT_TANGENTS: - return Vertices_Tangents.size(); + return static_cast(Vertices_Tangents.size()); default: - return Vertices_Standard.size(); + return static_cast(Vertices_Standard.size()); } } @@ -102,19 +101,19 @@ struct SSkinMeshBuffer : public IMeshBuffer //! Get pointer to index array const u16 *getIndices() const override { - return Indices.const_pointer(); + return Indices.data(); } //! Get pointer to index array u16 *getIndices() override { - return Indices.pointer(); + return Indices.data(); } //! Get index count u32 getIndexCount() const override { - return Indices.size(); + return static_cast(Indices.size()); } //! Get bounding box @@ -143,7 +142,7 @@ struct SSkinMeshBuffer : public IMeshBuffer BoundingBox.reset(0, 0, 0); else { BoundingBox.reset(Vertices_Standard[0].Pos); - for (u32 i = 1; i < Vertices_Standard.size(); ++i) + for (size_t i = 1; i < Vertices_Standard.size(); ++i) BoundingBox.addInternalPoint(Vertices_Standard[i].Pos); } break; @@ -153,7 +152,7 @@ struct SSkinMeshBuffer : public IMeshBuffer BoundingBox.reset(0, 0, 0); else { BoundingBox.reset(Vertices_2TCoords[0].Pos); - for (u32 i = 1; i < Vertices_2TCoords.size(); ++i) + for (size_t i = 1; i < Vertices_2TCoords.size(); ++i) BoundingBox.addInternalPoint(Vertices_2TCoords[i].Pos); } break; @@ -163,7 +162,7 @@ struct SSkinMeshBuffer : public IMeshBuffer BoundingBox.reset(0, 0, 0); else { BoundingBox.reset(Vertices_Tangents[0].Pos); - for (u32 i = 1; i < Vertices_Tangents.size(); ++i) + for (size_t i = 1; i < Vertices_Tangents.size(); ++i) BoundingBox.addInternalPoint(Vertices_Tangents[i].Pos); } break; @@ -181,12 +180,12 @@ struct SSkinMeshBuffer : public IMeshBuffer void convertTo2TCoords() { if (VertexType == video::EVT_STANDARD) { - for (u32 n = 0; n < Vertices_Standard.size(); ++n) { + for (const auto &Vertex_Standard : Vertices_Standard) { video::S3DVertex2TCoords Vertex; - Vertex.Color = Vertices_Standard[n].Color; - Vertex.Pos = Vertices_Standard[n].Pos; - Vertex.Normal = Vertices_Standard[n].Normal; - Vertex.TCoords = Vertices_Standard[n].TCoords; + Vertex.Color = Vertex_Standard.Color; + Vertex.Pos = Vertex_Standard.Pos; + Vertex.Normal = Vertex_Standard.Normal; + Vertex.TCoords = Vertex_Standard.TCoords; Vertices_2TCoords.push_back(Vertex); } Vertices_Standard.clear(); @@ -198,23 +197,23 @@ struct SSkinMeshBuffer : public IMeshBuffer void convertToTangents() { if (VertexType == video::EVT_STANDARD) { - for (u32 n = 0; n < Vertices_Standard.size(); ++n) { + for (const auto &Vertex_Standard : Vertices_Standard) { video::S3DVertexTangents Vertex; - Vertex.Color = Vertices_Standard[n].Color; - Vertex.Pos = Vertices_Standard[n].Pos; - Vertex.Normal = Vertices_Standard[n].Normal; - Vertex.TCoords = Vertices_Standard[n].TCoords; + Vertex.Color = Vertex_Standard.Color; + Vertex.Pos = Vertex_Standard.Pos; + Vertex.Normal = Vertex_Standard.Normal; + Vertex.TCoords = Vertex_Standard.TCoords; Vertices_Tangents.push_back(Vertex); } Vertices_Standard.clear(); VertexType = video::EVT_TANGENTS; } else if (VertexType == video::EVT_2TCOORDS) { - for (u32 n = 0; n < Vertices_2TCoords.size(); ++n) { + for (const auto &Vertex_2TCoords : Vertices_2TCoords) { video::S3DVertexTangents Vertex; - Vertex.Color = Vertices_2TCoords[n].Color; - Vertex.Pos = Vertices_2TCoords[n].Pos; - Vertex.Normal = Vertices_2TCoords[n].Normal; - Vertex.TCoords = Vertices_2TCoords[n].TCoords; + Vertex.Color = Vertex_2TCoords.Color; + Vertex.Pos = Vertex_2TCoords.Pos; + Vertex.Normal = Vertex_2TCoords.Normal; + Vertex.TCoords = Vertex_2TCoords.TCoords; Vertices_Tangents.push_back(Vertex); } Vertices_2TCoords.clear(); @@ -301,7 +300,10 @@ struct SSkinMeshBuffer : public IMeshBuffer } //! append the vertices and indices to the current buffer - void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) override {} + void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) override + { + _IRR_DEBUG_BREAK_IF(true); + } //! get the current hardware mapping hint for vertex buffers E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const override @@ -366,10 +368,10 @@ struct SSkinMeshBuffer : public IMeshBuffer //! Call this after changing the positions of any vertex. void boundingBoxNeedsRecalculated(void) { BoundingBoxNeedsRecalculated = true; } - core::array Vertices_Tangents; - core::array Vertices_2TCoords; - core::array Vertices_Standard; - core::array Indices; + std::vector Vertices_Tangents; + std::vector Vertices_2TCoords; + std::vector Vertices_Standard; + std::vector Indices; u32 ChangedID_Vertex; u32 ChangedID_Index; @@ -385,12 +387,12 @@ struct SSkinMeshBuffer : public IMeshBuffer //! Primitive type used for rendering (triangles, lines, ...) E_PRIMITIVE_TYPE PrimitiveType; + mutable void *HWBuffer; + // hardware mapping hint E_HARDWARE_MAPPING MappingHint_Vertex : 3; E_HARDWARE_MAPPING MappingHint_Index : 3; - mutable void *HWBuffer; - bool BoundingBoxNeedsRecalculated : 1; }; diff --git a/irr/include/irrArray.h b/irr/include/irrArray.h index b6f573a79..66978048f 100644 --- a/irr/include/irrArray.h +++ b/irr/include/irrArray.h @@ -167,13 +167,6 @@ public: return *this; } - array &operator=(std::vector &&other) - { - m_data = std::move(other); - is_sorted = false; - return *this; - } - //! Equality operator bool operator==(const array &other) const { @@ -400,16 +393,6 @@ public: std::swap(is_sorted, other.is_sorted); } - //! Pull the contents of this array as a vector. - // The array is left empty. - std::vector steal() - { - std::vector ret = std::move(m_data); - m_data.clear(); - is_sorted = true; - return ret; - } - typedef T value_type; typedef u32 size_type; diff --git a/irr/src/CAttributeImpl.h b/irr/src/CAttributeImpl.h index 9065a7342..e9436a407 100644 --- a/irr/src/CAttributeImpl.h +++ b/irr/src/CAttributeImpl.h @@ -3,9 +3,6 @@ // For conditions of distribution and use, see copyright notice in irrlicht.h #include "CAttributes.h" -#include "fast_atof.h" -#include "ITexture.h" -#include "IVideoDriver.h" namespace irr { diff --git a/irr/src/CAttributes.cpp b/irr/src/CAttributes.cpp index 2696d6bcf..b1509e455 100644 --- a/irr/src/CAttributes.cpp +++ b/irr/src/CAttributes.cpp @@ -12,61 +12,34 @@ namespace irr namespace io { -CAttributes::CAttributes(video::IVideoDriver *driver) : - Driver(driver) +CAttributes::CAttributes() { #ifdef _DEBUG setDebugName("CAttributes"); #endif - - if (Driver) - Driver->grab(); } CAttributes::~CAttributes() { clear(); - - if (Driver) - Driver->drop(); } //! Removes all attributes void CAttributes::clear() { - for (u32 i = 0; i < Attributes.size(); ++i) - Attributes[i]->drop(); - + for (auto it : Attributes) + delete it.second; Attributes.clear(); } -//! Returns attribute index from name, -1 if not found -s32 CAttributes::findAttribute(const c8 *attributeName) const -{ - for (u32 i = 0; i < Attributes.size(); ++i) - if (Attributes[i]->Name == attributeName) - return i; - - return -1; -} - -IAttribute *CAttributes::getAttributeP(const c8 *attributeName) const -{ - for (u32 i = 0; i < Attributes.size(); ++i) - if (Attributes[i]->Name == attributeName) - return Attributes[i]; - - return 0; -} - //! Sets a attribute as boolean value void CAttributes::setAttribute(const c8 *attributeName, bool value) { - IAttribute *att = getAttributeP(attributeName); - if (att) - att->setBool(value); - else { - Attributes.push_back(new CBoolAttribute(attributeName, value)); + auto it = Attributes.find(attributeName); + if (it != Attributes.end()) { + it->second->setBool(value); + } else { + Attributes[attributeName] = new CBoolAttribute(attributeName, value); } } @@ -76,9 +49,9 @@ void CAttributes::setAttribute(const c8 *attributeName, bool value) //! or 0 if attribute is not set. bool CAttributes::getAttributeAsBool(const c8 *attributeName, bool defaultNotFound) const { - const IAttribute *att = getAttributeP(attributeName); - if (att) - return att->getBool(); + auto it = Attributes.find(attributeName); + if (it != Attributes.end()) + return it->second->getBool(); else return defaultNotFound; } @@ -86,11 +59,11 @@ bool CAttributes::getAttributeAsBool(const c8 *attributeName, bool defaultNotFou //! Sets a attribute as integer value void CAttributes::setAttribute(const c8 *attributeName, s32 value) { - IAttribute *att = getAttributeP(attributeName); - if (att) - att->setInt(value); - else { - Attributes.push_back(new CIntAttribute(attributeName, value)); + auto it = Attributes.find(attributeName); + if (it != Attributes.end()) { + it->second->setInt(value); + } else { + Attributes[attributeName] = new CIntAttribute(attributeName, value); } } @@ -100,9 +73,9 @@ void CAttributes::setAttribute(const c8 *attributeName, s32 value) //! or 0 if attribute is not set. s32 CAttributes::getAttributeAsInt(const c8 *attributeName, irr::s32 defaultNotFound) const { - const IAttribute *att = getAttributeP(attributeName); - if (att) - return att->getInt(); + auto it = Attributes.find(attributeName); + if (it != Attributes.end()) + return it->second->getInt(); else return defaultNotFound; } @@ -110,11 +83,12 @@ s32 CAttributes::getAttributeAsInt(const c8 *attributeName, irr::s32 defaultNotF //! Sets a attribute as float value void CAttributes::setAttribute(const c8 *attributeName, f32 value) { - IAttribute *att = getAttributeP(attributeName); - if (att) - att->setFloat(value); - else - Attributes.push_back(new CFloatAttribute(attributeName, value)); + auto it = Attributes.find(attributeName); + if (it != Attributes.end()) { + it->second->setFloat(value); + } else { + Attributes[attributeName] = new CFloatAttribute(attributeName, value); + } } //! Gets a attribute as integer value @@ -123,27 +97,11 @@ void CAttributes::setAttribute(const c8 *attributeName, f32 value) //! or 0 if attribute is not set. f32 CAttributes::getAttributeAsFloat(const c8 *attributeName, irr::f32 defaultNotFound) const { - const IAttribute *att = getAttributeP(attributeName); - if (att) - return att->getFloat(); - - return defaultNotFound; -} - -//! Returns amount of string attributes set in this scene manager. -u32 CAttributes::getAttributeCount() const -{ - return Attributes.size(); -} - -//! Returns string attribute name by index. -//! \param index: Index value, must be between 0 and getStringAttributeCount()-1. -const c8 *CAttributes::getAttributeName(s32 index) const -{ - if ((u32)index >= Attributes.size()) - return 0; - - return Attributes[index]->Name.c_str(); + auto it = Attributes.find(attributeName); + if (it != Attributes.end()) + return it->second->getFloat(); + else + return defaultNotFound; } //! Returns the type of an attribute @@ -151,98 +109,17 @@ E_ATTRIBUTE_TYPE CAttributes::getAttributeType(const c8 *attributeName) const { E_ATTRIBUTE_TYPE ret = EAT_UNKNOWN; - const IAttribute *att = getAttributeP(attributeName); - if (att) - ret = att->getType(); + auto it = Attributes.find(attributeName); + if (it != Attributes.end()) + ret = it->second->getType(); return ret; } -//! Returns attribute type by index. -//! \param index: Index value, must be between 0 and getAttributeCount()-1. -E_ATTRIBUTE_TYPE CAttributes::getAttributeType(s32 index) const -{ - if ((u32)index >= Attributes.size()) - return EAT_UNKNOWN; - - return Attributes[index]->getType(); -} - -//! Gets an attribute as integer value -//! \param index: Index value, must be between 0 and getAttributeCount()-1. -s32 CAttributes::getAttributeAsInt(s32 index) const -{ - if ((u32)index < Attributes.size()) - return Attributes[index]->getInt(); - else - return 0; -} - -//! Gets an attribute as float value -//! \param index: Index value, must be between 0 and getAttributeCount()-1. -f32 CAttributes::getAttributeAsFloat(s32 index) const -{ - if ((u32)index < Attributes.size()) - return Attributes[index]->getFloat(); - else - return 0.f; -} - -//! Gets an attribute as boolean value -//! \param index: Index value, must be between 0 and getAttributeCount()-1. -bool CAttributes::getAttributeAsBool(s32 index) const -{ - bool ret = false; - - if ((u32)index < Attributes.size()) - ret = Attributes[index]->getBool(); - - return ret; -} - -//! Adds an attribute as integer -void CAttributes::addInt(const c8 *attributeName, s32 value) -{ - Attributes.push_back(new CIntAttribute(attributeName, value)); -} - -//! Adds an attribute as float -void CAttributes::addFloat(const c8 *attributeName, f32 value) -{ - Attributes.push_back(new CFloatAttribute(attributeName, value)); -} - -//! Adds an attribute as bool -void CAttributes::addBool(const c8 *attributeName, bool value) -{ - Attributes.push_back(new CBoolAttribute(attributeName, value)); -} - //! Returns if an attribute with a name exists bool CAttributes::existsAttribute(const c8 *attributeName) const { - return getAttributeP(attributeName) != 0; -} - -//! Sets an attribute as boolean value -void CAttributes::setAttribute(s32 index, bool value) -{ - if ((u32)index < Attributes.size()) - Attributes[index]->setBool(value); -} - -//! Sets an attribute as integer value -void CAttributes::setAttribute(s32 index, s32 value) -{ - if ((u32)index < Attributes.size()) - Attributes[index]->setInt(value); -} - -//! Sets a attribute as float value -void CAttributes::setAttribute(s32 index, f32 value) -{ - if ((u32)index < Attributes.size()) - Attributes[index]->setFloat(value); + return Attributes.find(attributeName) != Attributes.end(); } } // end namespace io diff --git a/irr/src/CAttributes.h b/irr/src/CAttributes.h index 0fe2739b6..8ea3ecf19 100644 --- a/irr/src/CAttributes.h +++ b/irr/src/CAttributes.h @@ -4,16 +4,14 @@ #pragma once +#include +#include #include "IAttributes.h" #include "IAttribute.h" namespace irr { -namespace video -{ -class ITexture; -class IVideoDriver; -} + namespace io { @@ -21,30 +19,16 @@ namespace io class CAttributes : public IAttributes { public: - CAttributes(video::IVideoDriver *driver = 0); + CAttributes(); ~CAttributes(); - //! Returns amount of attributes in this collection of attributes. - u32 getAttributeCount() const override; - - //! Returns attribute name by index. - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - const c8 *getAttributeName(s32 index) const override; - //! Returns the type of an attribute //! \param attributeName: Name for the attribute E_ATTRIBUTE_TYPE getAttributeType(const c8 *attributeName) const override; - //! Returns attribute type by index. - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - E_ATTRIBUTE_TYPE getAttributeType(s32 index) const override; - //! Returns if an attribute with a name exists bool existsAttribute(const c8 *attributeName) const override; - //! Returns attribute index from name, -1 if not found - s32 findAttribute(const c8 *attributeName) const override; - //! Removes all attributes void clear() override; @@ -55,7 +39,9 @@ public: */ //! Adds an attribute as integer - void addInt(const c8 *attributeName, s32 value) override; + void addInt(const c8 *attributeName, s32 value) override { + setAttribute(attributeName, value); + } //! Sets an attribute as integer value void setAttribute(const c8 *attributeName, s32 value) override; @@ -66,13 +52,6 @@ public: //! \return Returns value of the attribute previously set by setAttribute() s32 getAttributeAsInt(const c8 *attributeName, irr::s32 defaultNotFound = 0) const override; - //! Gets an attribute as integer value - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - s32 getAttributeAsInt(s32 index) const override; - - //! Sets an attribute as integer value - void setAttribute(s32 index, s32 value) override; - /* Float Attribute @@ -80,7 +59,9 @@ public: */ //! Adds an attribute as float - void addFloat(const c8 *attributeName, f32 value) override; + void addFloat(const c8 *attributeName, f32 value) override { + setAttribute(attributeName, value); + } //! Sets a attribute as float value void setAttribute(const c8 *attributeName, f32 value) override; @@ -91,19 +72,14 @@ public: //! \return Returns value of the attribute previously set by setAttribute() f32 getAttributeAsFloat(const c8 *attributeName, irr::f32 defaultNotFound = 0.f) const override; - //! Gets an attribute as float value - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - f32 getAttributeAsFloat(s32 index) const override; - - //! Sets an attribute as float value - void setAttribute(s32 index, f32 value) override; - /* Bool Attribute */ //! Adds an attribute as bool - void addBool(const c8 *attributeName, bool value) override; + void addBool(const c8 *attributeName, bool value) override { + setAttribute(attributeName, value); + } //! Sets an attribute as boolean value void setAttribute(const c8 *attributeName, bool value) override; @@ -114,19 +90,12 @@ public: //! \return Returns value of the attribute previously set by setAttribute() bool getAttributeAsBool(const c8 *attributeName, bool defaultNotFound = false) const override; - //! Gets an attribute as boolean value - //! \param index: Index value, must be between 0 and getAttributeCount()-1. - bool getAttributeAsBool(s32 index) const override; - - //! Sets an attribute as boolean value - void setAttribute(s32 index, bool value) override; - protected: - core::array Attributes; + typedef std::basic_string string; - IAttribute *getAttributeP(const c8 *attributeName) const; - - video::IVideoDriver *Driver; + // specify a comparator so we can directly look up in the map with const c8* + // (works since C++14) + std::map> Attributes; }; } // end namespace io diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index 6ee24af0e..008169bd7 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -433,7 +433,7 @@ bool CB3DMeshFileLoader::readChunkTRIS(scene::SSkinMeshBuffer *meshBuffer, u32 m } const s32 memoryNeeded = B3dStack.getLast().length / sizeof(s32); - meshBuffer->Indices.reallocate(memoryNeeded + meshBuffer->Indices.size() + 1); + meshBuffer->Indices.reserve(memoryNeeded + meshBuffer->Indices.size() + 1); while ((B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) // this chunk repeats { diff --git a/irr/src/CBillboardSceneNode.cpp b/irr/src/CBillboardSceneNode.cpp index ddb9d465a..7be139f96 100644 --- a/irr/src/CBillboardSceneNode.cpp +++ b/irr/src/CBillboardSceneNode.cpp @@ -26,8 +26,8 @@ CBillboardSceneNode::CBillboardSceneNode(ISceneNode *parent, ISceneManager *mgr, setSize(size); - Buffer->Vertices.set_used(4); - Buffer->Indices.set_used(6); + Buffer->Vertices.resize(4); + Buffer->Indices.resize(6); Buffer->Indices[0] = 0; Buffer->Indices[1] = 2; @@ -114,7 +114,7 @@ void CBillboardSceneNode::updateMesh(const irr::scene::ICameraSceneNode *camera) view *= -1.0f; - core::array &vertices = Buffer->Vertices; + auto *vertices = Buffer->Vertices.data(); for (s32 i = 0; i < 4; ++i) vertices[i].Normal = view; diff --git a/irr/src/CFileSystem.cpp b/irr/src/CFileSystem.cpp index bffb703dd..386bb389c 100644 --- a/irr/src/CFileSystem.cpp +++ b/irr/src/CFileSystem.cpp @@ -58,14 +58,8 @@ CFileSystem::CFileSystem() //! destructor CFileSystem::~CFileSystem() { - u32 i; - - for (i = 0; i < FileArchives.size(); ++i) { - FileArchives[i]->drop(); - } - - for (i = 0; i < ArchiveLoader.size(); ++i) { - ArchiveLoader[i]->drop(); + for (auto *it : ArchiveLoader) { + it->drop(); } } @@ -75,15 +69,6 @@ IReadFile *CFileSystem::createAndOpenFile(const io::path &filename) if (filename.empty()) return 0; - IReadFile *file = 0; - u32 i; - - for (i = 0; i < FileArchives.size(); ++i) { - file = FileArchives[i]->createAndOpenFile(filename); - if (file) - return file; - } - // Create the file using an absolute path so that it matches // the scheme used by CNullDriver::getTexture(). return CReadFile::createReadFile(getAbsolutePath(filename)); @@ -150,241 +135,6 @@ IArchiveLoader *CFileSystem::getArchiveLoader(u32 index) const return 0; } -//! move the hirarchy of the filesystem. moves sourceIndex relative up or down -bool CFileSystem::moveFileArchive(u32 sourceIndex, s32 relative) -{ - bool r = false; - const s32 dest = (s32)sourceIndex + relative; - const s32 dir = relative < 0 ? -1 : 1; - const s32 sourceEnd = ((s32)FileArchives.size()) - 1; - IFileArchive *t; - - for (s32 s = (s32)sourceIndex; s != dest; s += dir) { - if (s < 0 || s > sourceEnd || s + dir < 0 || s + dir > sourceEnd) - continue; - - t = FileArchives[s + dir]; - FileArchives[s + dir] = FileArchives[s]; - FileArchives[s] = t; - r = true; - } - return r; -} - -//! Adds an archive to the file system. -bool CFileSystem::addFileArchive(const io::path &filename, bool ignoreCase, - bool ignorePaths, E_FILE_ARCHIVE_TYPE archiveType, - const core::stringc &password, - IFileArchive **retArchive) -{ - IFileArchive *archive = 0; - bool ret = false; - - // see if archive is already added - - s32 i; - - // do we know what type it should be? - if (archiveType == EFAT_UNKNOWN) { - // try to load archive based on file name - for (i = ArchiveLoader.size() - 1; i >= 0; --i) { - if (ArchiveLoader[i]->isALoadableFileFormat(filename)) { - archive = ArchiveLoader[i]->createArchive(filename, ignoreCase, ignorePaths); - if (archive) - break; - } - } - - // try to load archive based on content - if (!archive) { - io::IReadFile *file = createAndOpenFile(filename); - if (file) { - for (i = ArchiveLoader.size() - 1; i >= 0; --i) { - file->seek(0); - if (ArchiveLoader[i]->isALoadableFileFormat(file)) { - file->seek(0); - archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); - if (archive) - break; - } - } - file->drop(); - } - } - } else { - // try to open archive based on archive loader type - - io::IReadFile *file = 0; - - for (i = ArchiveLoader.size() - 1; i >= 0; --i) { - if (ArchiveLoader[i]->isALoadableFileFormat(archiveType)) { - // attempt to open file - if (!file) - file = createAndOpenFile(filename); - - // is the file open? - if (file) { - // attempt to open archive - file->seek(0); - if (ArchiveLoader[i]->isALoadableFileFormat(file)) { - file->seek(0); - archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); - if (archive) - break; - } - } else { - // couldn't open file - break; - } - } - } - - // if open, close the file - if (file) - file->drop(); - } - - if (archive) { - FileArchives.push_back(archive); - if (password.size()) - archive->Password = password; - if (retArchive) - *retArchive = archive; - ret = true; - } else { - os::Printer::log("Could not create archive for", filename, ELL_ERROR); - } - - return ret; -} - -bool CFileSystem::addFileArchive(IReadFile *file, bool ignoreCase, - bool ignorePaths, E_FILE_ARCHIVE_TYPE archiveType, - const core::stringc &password, IFileArchive **retArchive) -{ - if (!file) - return false; - - if (file) { - IFileArchive *archive = 0; - s32 i; - - if (archiveType == EFAT_UNKNOWN) { - // try to load archive based on file name - for (i = ArchiveLoader.size() - 1; i >= 0; --i) { - if (ArchiveLoader[i]->isALoadableFileFormat(file->getFileName())) { - archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); - if (archive) - break; - } - } - - // try to load archive based on content - if (!archive) { - for (i = ArchiveLoader.size() - 1; i >= 0; --i) { - file->seek(0); - if (ArchiveLoader[i]->isALoadableFileFormat(file)) { - file->seek(0); - archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); - if (archive) - break; - } - } - } - } else { - // try to open archive based on archive loader type - for (i = ArchiveLoader.size() - 1; i >= 0; --i) { - if (ArchiveLoader[i]->isALoadableFileFormat(archiveType)) { - // attempt to open archive - file->seek(0); - if (ArchiveLoader[i]->isALoadableFileFormat(file)) { - file->seek(0); - archive = ArchiveLoader[i]->createArchive(file, ignoreCase, ignorePaths); - if (archive) - break; - } - } - } - } - - if (archive) { - FileArchives.push_back(archive); - if (password.size()) - archive->Password = password; - if (retArchive) - *retArchive = archive; - return true; - } else { - os::Printer::log("Could not create archive for", file->getFileName(), ELL_ERROR); - } - } - - return false; -} - -//! Adds an archive to the file system. -bool CFileSystem::addFileArchive(IFileArchive *archive) -{ - if (archive) { - for (u32 i = 0; i < FileArchives.size(); ++i) { - if (archive == FileArchives[i]) { - return false; - } - } - FileArchives.push_back(archive); - archive->grab(); - - return true; - } - - return false; -} - -//! removes an archive from the file system. -bool CFileSystem::removeFileArchive(u32 index) -{ - bool ret = false; - if (index < FileArchives.size()) { - FileArchives[index]->drop(); - FileArchives.erase(index); - ret = true; - } - return ret; -} - -//! removes an archive from the file system. -bool CFileSystem::removeFileArchive(const io::path &filename) -{ - const path absPath = getAbsolutePath(filename); - for (u32 i = 0; i < FileArchives.size(); ++i) { - if (absPath == FileArchives[i]->getFileList()->getPath()) - return removeFileArchive(i); - } - return false; -} - -//! Removes an archive from the file system. -bool CFileSystem::removeFileArchive(const IFileArchive *archive) -{ - for (u32 i = 0; i < FileArchives.size(); ++i) { - if (archive == FileArchives[i]) { - return removeFileArchive(i); - } - } - return false; -} - -//! gets an archive -u32 CFileSystem::getFileArchiveCount() const -{ - return FileArchives.size(); -} - -IFileArchive *CFileSystem::getFileArchive(u32 index) -{ - return index < getFileArchiveCount() ? FileArchives[index] : 0; -} - //! Returns the string of the current working directory const io::path &CFileSystem::getWorkingDirectory() { @@ -711,17 +461,6 @@ IFileList *CFileSystem::createFileList() //! parent r->addItem(Path + _IRR_TEXT(".."), 0, 0, true, 0); - - //! merge archives - for (u32 i = 0; i < FileArchives.size(); ++i) { - const IFileList *merge = FileArchives[i]->getFileList(); - - for (u32 j = 0; j < merge->getFileCount(); ++j) { - if (core::isInSameDirectory(Path, merge->getFullFileName(j)) == 0) { - r->addItem(merge->getFullFileName(j), merge->getFileOffset(j), merge->getFileSize(j), merge->isDirectory(j), 0); - } - } - } } if (r) @@ -738,10 +477,6 @@ IFileList *CFileSystem::createEmptyFileList(const io::path &path, bool ignoreCas //! determines if a file exists and would be able to be opened. bool CFileSystem::existFile(const io::path &filename) const { - for (u32 i = 0; i < FileArchives.size(); ++i) - if (FileArchives[i]->getFileList()->findFile(filename) != -1) - return true; - #if defined(_MSC_VER) return (_access(filename.c_str(), 0) != -1); #elif defined(F_OK) diff --git a/irr/src/CFileSystem.h b/irr/src/CFileSystem.h index 18bc7ff3f..208a1ac41 100644 --- a/irr/src/CFileSystem.h +++ b/irr/src/CFileSystem.h @@ -4,8 +4,8 @@ #pragma once +#include #include "IFileSystem.h" -#include "irrArray.h" namespace irr { @@ -41,25 +41,6 @@ public: //! Opens a file for write access. IWriteFile *createAndWriteFile(const io::path &filename, bool append = false) override; - //! Adds an archive to the file system. - virtual bool addFileArchive(const io::path &filename, - bool ignoreCase = true, bool ignorePaths = true, - E_FILE_ARCHIVE_TYPE archiveType = EFAT_UNKNOWN, - const core::stringc &password = "", - IFileArchive **retArchive = 0) override; - - //! Adds an archive to the file system. - virtual bool addFileArchive(IReadFile *file, bool ignoreCase = true, - bool ignorePaths = true, - E_FILE_ARCHIVE_TYPE archiveType = EFAT_UNKNOWN, - const core::stringc &password = "", - IFileArchive **retArchive = 0) override; - - //! Adds an archive to the file system. - bool addFileArchive(IFileArchive *archive) override; - - //! move the hirarchy of the filesystem. moves sourceIndex relative up or down - bool moveFileArchive(u32 sourceIndex, s32 relative) override; //! Adds an external archive loader to the engine. void addArchiveLoader(IArchiveLoader *loader) override; @@ -70,21 +51,6 @@ public: //! Gets the archive loader by index. IArchiveLoader *getArchiveLoader(u32 index) const override; - //! gets the file archive count - u32 getFileArchiveCount() const override; - - //! gets an archive - IFileArchive *getFileArchive(u32 index) override; - - //! removes an archive from the file system. - bool removeFileArchive(u32 index) override; - - //! removes an archive from the file system. - bool removeFileArchive(const io::path &filename) override; - - //! Removes an archive from the file system. - bool removeFileArchive(const IFileArchive *archive) override; - //! Returns the string of the current working directory const io::path &getWorkingDirectory() override; @@ -129,9 +95,7 @@ private: //! WorkingDirectory for Native and Virtual filesystems io::path WorkingDirectory[2]; //! currently attached ArchiveLoaders - core::array ArchiveLoader; - //! currently attached Archives - core::array FileArchives; + std::vector ArchiveLoader; }; } // end namespace irr diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index 3309fea3f..db6c39812 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -132,48 +132,30 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const case video::EVT_STANDARD: { SMeshBuffer *buffer = new SMeshBuffer(); buffer->Material = mb->getMaterial(); - const u32 vcount = mb->getVertexCount(); - buffer->Vertices.reallocate(vcount); - video::S3DVertex *vertices = (video::S3DVertex *)mb->getVertices(); - for (u32 i = 0; i < vcount; ++i) - buffer->Vertices.push_back(vertices[i]); - const u32 icount = mb->getIndexCount(); - buffer->Indices.reallocate(icount); - const u16 *indices = mb->getIndices(); - for (u32 i = 0; i < icount; ++i) - buffer->Indices.push_back(indices[i]); + auto *vt = static_cast(mb->getVertices()); + buffer->Vertices.insert(buffer->Vertices.end(), vt, vt + mb->getVertexCount()); + auto *indices = mb->getIndices(); + buffer->Indices.insert(buffer->Indices.end(), indices, indices + mb->getIndexCount()); clone->addMeshBuffer(buffer); buffer->drop(); } break; case video::EVT_2TCOORDS: { SMeshBufferLightMap *buffer = new SMeshBufferLightMap(); buffer->Material = mb->getMaterial(); - const u32 vcount = mb->getVertexCount(); - buffer->Vertices.reallocate(vcount); - video::S3DVertex2TCoords *vertices = (video::S3DVertex2TCoords *)mb->getVertices(); - for (u32 i = 0; i < vcount; ++i) - buffer->Vertices.push_back(vertices[i]); - const u32 icount = mb->getIndexCount(); - buffer->Indices.reallocate(icount); - const u16 *indices = mb->getIndices(); - for (u32 i = 0; i < icount; ++i) - buffer->Indices.push_back(indices[i]); + auto *vt = static_cast(mb->getVertices()); + buffer->Vertices.insert(buffer->Vertices.end(), vt, vt + mb->getVertexCount()); + auto *indices = mb->getIndices(); + buffer->Indices.insert(buffer->Indices.end(), indices, indices + mb->getIndexCount()); clone->addMeshBuffer(buffer); buffer->drop(); } break; case video::EVT_TANGENTS: { SMeshBufferTangents *buffer = new SMeshBufferTangents(); buffer->Material = mb->getMaterial(); - const u32 vcount = mb->getVertexCount(); - buffer->Vertices.reallocate(vcount); - video::S3DVertexTangents *vertices = (video::S3DVertexTangents *)mb->getVertices(); - for (u32 i = 0; i < vcount; ++i) - buffer->Vertices.push_back(vertices[i]); - const u32 icount = mb->getIndexCount(); - buffer->Indices.reallocate(icount); - const u16 *indices = mb->getIndices(); - for (u32 i = 0; i < icount; ++i) - buffer->Indices.push_back(indices[i]); + auto *vt = static_cast(mb->getVertices()); + buffer->Vertices.insert(buffer->Vertices.end(), vt, vt + mb->getVertexCount()); + auto *indices = mb->getIndices(); + buffer->Indices.insert(buffer->Indices.end(), indices, indices + mb->getIndexCount()); clone->addMeshBuffer(buffer); buffer->drop(); } break; diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index 92450e5d7..c1cecbe8c 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -64,7 +64,6 @@ CNullDriver::CNullDriver(io::IFileSystem *io, const core::dimension2d &scre DriverAttributes->addInt("MaxTextures", MATERIAL_MAX_TEXTURES); DriverAttributes->addInt("MaxSupportedTextures", MATERIAL_MAX_TEXTURES); DriverAttributes->addInt("MaxAnisotropy", 1); - // DriverAttributes->addInt("MaxUserClipPlanes", 0); // DriverAttributes->addInt("MaxAuxBuffers", 0); DriverAttributes->addInt("MaxMultipleRenderTargets", 1); DriverAttributes->addInt("MaxIndices", -1); @@ -361,7 +360,7 @@ ITexture *CNullDriver::addTextureCubemap(const io::path &name, IImage *imagePosX ITexture *t = 0; - core::array imageArray(6); + std::vector imageArray; imageArray.push_back(imagePosX); imageArray.push_back(imageNegX); imageArray.push_back(imagePosY); @@ -391,7 +390,7 @@ ITexture *CNullDriver::addTextureCubemap(const irr::u32 sideLen, const io::path return 0; } - core::array imageArray(6); + std::vector imageArray; for (int i = 0; i < 6; ++i) imageArray.push_back(new CImage(format, core::dimension2du(sideLen, sideLen))); @@ -548,7 +547,7 @@ ITexture *CNullDriver::createDeviceDependentTexture(const io::path &name, IImage return dummy; } -ITexture *CNullDriver::createDeviceDependentTextureCubemap(const io::path &name, const core::array &image) +ITexture *CNullDriver::createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) { return new SDummyTexture(name, ETT_CUBEMAP); } @@ -913,17 +912,17 @@ bool CNullDriver::checkImage(IImage *image) const return true; } -bool CNullDriver::checkImage(const core::array &image) const +bool CNullDriver::checkImage(const std::vector &image) const { - if (!image.size()) + if (image.empty()) return false; ECOLOR_FORMAT lastFormat = image[0]->getColorFormat(); - core::dimension2d lastSize = image[0]->getDimension(); + auto lastSize = image[0]->getDimension(); - for (u32 i = 0; i < image.size(); ++i) { + for (size_t i = 0; i < image.size(); ++i) { ECOLOR_FORMAT format = image[i]->getColorFormat(); - core::dimension2d size = image[i]->getDimension(); + auto size = image[i]->getDimension(); if (!checkImage(image[i])) return false; @@ -1699,22 +1698,6 @@ IVideoDriver *createNullDriver(io::IFileSystem *io, const core::dimension2d return nullDriver; } -//! Set/unset a clipping plane. -//! There are at least 6 clipping planes available for the user to set at will. -//! \param index: The plane index. Must be between 0 and MaxUserClipPlanes. -//! \param plane: The plane itself. -//! \param enable: If true, enable the clipping plane else disable it. -bool CNullDriver::setClipPlane(u32 index, const core::plane3df &plane, bool enable) -{ - return false; -} - -//! Enable/disable a clipping plane. -void CNullDriver::enableClipPlane(u32 index, bool enable) -{ - // not necessary -} - void CNullDriver::setMinHardwareBufferVertexCount(u32 count) { MinVertexCountForVBO = count; diff --git a/irr/src/CNullDriver.h b/irr/src/CNullDriver.h index d47212c0f..30911b7a6 100644 --- a/irr/src/CNullDriver.h +++ b/irr/src/CNullDriver.h @@ -494,19 +494,6 @@ public: //! looks if the image is already loaded video::ITexture *findTexture(const io::path &filename) override; - //! Set/unset a clipping plane. - //! There are at least 6 clipping planes available for the user to set at will. - //! \param index: The plane index. Must be between 0 and MaxUserClipPlanes. - //! \param plane: The plane itself. - //! \param enable: If true, enable the clipping plane else disable it. - bool setClipPlane(u32 index, const core::plane3df &plane, bool enable = false) override; - - //! Enable/disable a clipping plane. - //! There are at least 6 clipping planes available for the user to set at will. - //! \param index: The plane index. Must be between 0 and MaxUserClipPlanes. - //! \param enable: If true, enable the clipping plane else disable it. - void enableClipPlane(u32 index, bool enable) override; - //! Returns the graphics card vendor name. core::stringc getVendorInfo() override { return "Not available on this driver."; } @@ -565,14 +552,14 @@ protected: virtual ITexture *createDeviceDependentTexture(const io::path &name, IImage *image); - virtual ITexture *createDeviceDependentTextureCubemap(const io::path &name, const core::array &image); + virtual ITexture *createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image); //! checks triangle count and print warning if wrong bool checkPrimitiveCount(u32 prmcnt) const; bool checkImage(IImage *image) const; - bool checkImage(const core::array &image) const; + bool checkImage(const std::vector &image) const; // adds a material renderer and drops it afterwards. To be used for internal creation s32 addAndDropMaterialRenderer(IMaterialRenderer *m); diff --git a/irr/src/COGLESDriver.cpp b/irr/src/COGLESDriver.cpp index 524aedd50..d5feda58e 100644 --- a/irr/src/COGLESDriver.cpp +++ b/irr/src/COGLESDriver.cpp @@ -103,14 +103,6 @@ bool COGLES1Driver::genericDriverInit(const core::dimension2d &screenSize, glPixelStorei(GL_PACK_ALIGNMENT, 1); - UserClipPlane.reallocate(MaxUserClipPlanes); - UserClipPlaneEnabled.resize(MaxUserClipPlanes); - - for (s32 i = 0; i < MaxUserClipPlanes; ++i) { - UserClipPlane.push_back(core::plane3df()); - UserClipPlaneEnabled[i] = false; - } - for (s32 i = 0; i < ETS_COUNT; ++i) setTransform(static_cast(i), core::IdentityMatrix); @@ -195,10 +187,6 @@ void COGLES1Driver::setTransform(E_TRANSFORMATION_STATE state, const core::matri // OGLES1 only has a model matrix, view and world is not existent. so lets fake these two. glMatrixMode(GL_MODELVIEW); glLoadMatrixf((Matrices[ETS_VIEW] * Matrices[ETS_WORLD]).pointer()); - // we have to update the clip planes to the latest view matrix - for (u32 i = 0; i < MaxUserClipPlanes; ++i) - if (UserClipPlaneEnabled[i]) - uploadClipPlane(i); } break; case ETS_PROJECTION: { GLfloat glmat[16]; @@ -1149,15 +1137,14 @@ inline void COGLES1Driver::getGLTextureMatrix(GLfloat *o, const core::matrix4 &m ITexture *COGLES1Driver::createDeviceDependentTexture(const io::path &name, IImage *image) { - core::array imageArray(1); - imageArray.push_back(image); + std::vector tmp { image }; - COGLES1Texture *texture = new COGLES1Texture(name, imageArray, ETT_2D, this); + COGLES1Texture *texture = new COGLES1Texture(name, tmp, ETT_2D, this); return texture; } -ITexture *COGLES1Driver::createDeviceDependentTextureCubemap(const io::path &name, const core::array &image) +ITexture *COGLES1Driver::createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) { COGLES1Texture *texture = new COGLES1Texture(name, image, ETT_CUBEMAP, this); @@ -2158,44 +2145,6 @@ void COGLES1Driver::removeTexture(ITexture *texture) CNullDriver::removeTexture(texture); } -//! Set/unset a clipping plane. -bool COGLES1Driver::setClipPlane(u32 index, const core::plane3df &plane, bool enable) -{ - if (index >= MaxUserClipPlanes) - return false; - - UserClipPlane[index] = plane; - enableClipPlane(index, enable); - return true; -} - -void COGLES1Driver::uploadClipPlane(u32 index) -{ - // opengl needs an array of doubles for the plane equation - float clip_plane[4]; - clip_plane[0] = UserClipPlane[index].Normal.X; - clip_plane[1] = UserClipPlane[index].Normal.Y; - clip_plane[2] = UserClipPlane[index].Normal.Z; - clip_plane[3] = UserClipPlane[index].D; - glClipPlanef(GL_CLIP_PLANE0 + index, clip_plane); -} - -//! Enable/disable a clipping plane. -void COGLES1Driver::enableClipPlane(u32 index, bool enable) -{ - if (index >= MaxUserClipPlanes) - return; - if (enable) { - if (!UserClipPlaneEnabled[index]) { - uploadClipPlane(index); - glEnable(GL_CLIP_PLANE0 + index); - } - } else - glDisable(GL_CLIP_PLANE0 + index); - - UserClipPlaneEnabled[index] = enable; -} - core::dimension2du COGLES1Driver::getMaxTextureSize() const { return core::dimension2du(MaxTextureSize, MaxTextureSize); diff --git a/irr/src/COGLESDriver.h b/irr/src/COGLESDriver.h index 29e0d94f1..8b9152cea 100644 --- a/irr/src/COGLESDriver.h +++ b/irr/src/COGLESDriver.h @@ -215,12 +215,6 @@ public: //! checks if an OpenGL error has happened and prints it (+ some internal code which is usually the line number) bool testGLError(int code = 0); - //! Set/unset a clipping plane. - bool setClipPlane(u32 index, const core::plane3df &plane, bool enable = false) override; - - //! Enable/disable a clipping plane. - void enableClipPlane(u32 index, bool enable) override; - //! Returns the graphics card vendor name. core::stringc getVendorInfo() override { @@ -250,14 +244,12 @@ public: COGLES1CacheHandler *getCacheHandler() const; private: - void uploadClipPlane(u32 index); - //! inits the opengl-es driver bool genericDriverInit(const core::dimension2d &screenSize, bool stencilBuffer); ITexture *createDeviceDependentTexture(const io::path &name, IImage *image) override; - ITexture *createDeviceDependentTextureCubemap(const io::path &name, const core::array &image) override; + ITexture *createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) override; //! creates a transposed matrix in supplied GLfloat array to pass to OGLES1 inline void getGLMatrix(GLfloat gl_matrix[16], const core::matrix4 &m); @@ -306,8 +298,6 @@ private: u8 AntiAlias; SMaterial Material, LastMaterial; - core::array UserClipPlane; - std::vector UserClipPlaneEnabled; core::stringc VendorName; diff --git a/irr/src/COGLESExtensionHandler.cpp b/irr/src/COGLESExtensionHandler.cpp index 82f8e9261..866a984d8 100644 --- a/irr/src/COGLESExtensionHandler.cpp +++ b/irr/src/COGLESExtensionHandler.cpp @@ -25,7 +25,7 @@ namespace video COGLES1ExtensionHandler::COGLES1ExtensionHandler() : COGLESCoreExtensionHandler(), - MaxUserClipPlanes(0), MaxLights(0), pGlBlendEquationOES(0), pGlBlendFuncSeparateOES(0), + MaxLights(0), pGlBlendEquationOES(0), pGlBlendFuncSeparateOES(0), pGlBindFramebufferOES(0), pGlDeleteFramebuffersOES(0), pGlGenFramebuffersOES(0), pGlCheckFramebufferStatusOES(0), pGlFramebufferTexture2DOES(0), pGlGenerateMipmapOES(0) @@ -45,11 +45,6 @@ void COGLES1ExtensionHandler::initExtensions() GLint val = 0; - if (Version > 100 || FeatureAvailable[IRR_GL_IMG_user_clip_plane]) { - glGetIntegerv(GL_MAX_CLIP_PLANES, &val); - MaxUserClipPlanes = static_cast(val); - } - glGetIntegerv(GL_MAX_LIGHTS, &val); MaxLights = static_cast(val); diff --git a/irr/src/COGLESExtensionHandler.h b/irr/src/COGLESExtensionHandler.h index d9e1ac4bc..a316afaad 100644 --- a/irr/src/COGLESExtensionHandler.h +++ b/irr/src/COGLESExtensionHandler.h @@ -171,7 +171,6 @@ public: } protected: - u8 MaxUserClipPlanes; u8 MaxLights; PFNGLBLENDEQUATIONOESPROC pGlBlendEquationOES; diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 79a855cc1..218eabece 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -4,7 +4,7 @@ #pragma once -#include "irrArray.h" +#include #include "SMaterialLayer.h" #include "ITexture.h" #include "EDriverFeatures.h" @@ -43,19 +43,19 @@ public: bool IsCached; }; - COpenGLCoreTexture(const io::path &name, const core::array &images, E_TEXTURE_TYPE type, TOpenGLDriver *driver) : + COpenGLCoreTexture(const io::path &name, const std::vector &srcImages, E_TEXTURE_TYPE type, TOpenGLDriver *driver) : ITexture(name, type), Driver(driver), TextureType(GL_TEXTURE_2D), TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false), MipLevelStored(0), LegacyAutoGenerateMipMaps(false) { - _IRR_DEBUG_BREAK_IF(images.size() == 0) + _IRR_DEBUG_BREAK_IF(srcImages.empty()) DriverType = Driver->getDriverType(); TextureType = TextureTypeIrrToGL(Type); HasMipMaps = Driver->getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); KeepImage = Driver->getTextureCreationFlag(ETCF_ALLOW_MEMORY_COPY); - getImageValues(images[0]); + getImageValues(srcImages[0]); if (!InternalFormat) return; @@ -71,22 +71,22 @@ public: os::Printer::log(lbuf, ELL_DEBUG); #endif - const core::array *tmpImages = &images; + const auto *tmpImages = &srcImages; if (KeepImage || OriginalSize != Size || OriginalColorFormat != ColorFormat) { - Images.set_used(images.size()); + Images.resize(srcImages.size()); - for (u32 i = 0; i < images.size(); ++i) { + for (size_t i = 0; i < srcImages.size(); ++i) { Images[i] = Driver->createImage(ColorFormat, Size); - if (images[i]->getDimension() == Size) - images[i]->copyTo(Images[i]); + if (srcImages[i]->getDimension() == Size) + srcImages[i]->copyTo(Images[i]); else - images[i]->copyToScaling(Images[i]); + srcImages[i]->copyToScaling(Images[i]); - if (images[i]->getMipMapsData()) { + if (srcImages[i]->getMipMapsData()) { if (OriginalSize == Size && OriginalColorFormat == ColorFormat) { - Images[i]->setMipMapsData(images[i]->getMipMapsData(), false); + Images[i]->setMipMapsData(srcImages[i]->getMipMapsData(), false); } else { // TODO: handle at least mipmap with changing color format os::Printer::log("COpenGLCoreTexture: Can't handle format changes for mipmap data. Mipmap data dropped", ELL_WARNING); @@ -118,19 +118,19 @@ public: TEST_GL_ERROR(Driver); - for (u32 i = 0; i < (*tmpImages).size(); ++i) + for (size_t i = 0; i < tmpImages->size(); ++i) uploadTexture(true, i, 0, (*tmpImages)[i]->getData()); if (HasMipMaps && !LegacyAutoGenerateMipMaps) { // Create mipmaps (either from image mipmaps or generate them) - for (u32 i = 0; i < (*tmpImages).size(); ++i) { + for (size_t i = 0; i < tmpImages->size(); ++i) { void *mipmapsData = (*tmpImages)[i]->getMipMapsData(); regenerateMipMapLevels(mipmapsData, i); } } if (!KeepImage) { - for (u32 i = 0; i < Images.size(); ++i) + for (size_t i = 0; i < Images.size(); ++i) Images[i]->drop(); Images.clear(); @@ -227,8 +227,8 @@ public: if (LockImage) LockImage->drop(); - for (u32 i = 0; i < Images.size(); ++i) - Images[i]->drop(); + for (auto *image : Images) + image->drop(); } void *lock(E_TEXTURE_LOCK_MODE mode = ETLM_READ_WRITE, u32 mipmapLevel = 0, u32 layer = 0, E_TEXTURE_LOCK_FLAGS lockFlags = ETLF_FLIP_Y_UP_RTT) override @@ -621,7 +621,7 @@ protected: u32 LockLayer; bool KeepImage; - core::array Images; + std::vector Images; u8 MipLevelStored; bool LegacyAutoGenerateMipMaps; diff --git a/irr/src/COpenGLDriver.cpp b/irr/src/COpenGLDriver.cpp index 1ad66aef9..1c6342090 100644 --- a/irr/src/COpenGLDriver.cpp +++ b/irr/src/COpenGLDriver.cpp @@ -122,7 +122,6 @@ bool COpenGLDriver::genericDriverInit() DriverAttributes->setAttribute("MaxSupportedTextures", (s32)Feature.MaxTextureUnits); DriverAttributes->setAttribute("MaxLights", MaxLights); DriverAttributes->setAttribute("MaxAnisotropy", MaxAnisotropy); - DriverAttributes->setAttribute("MaxUserClipPlanes", MaxUserClipPlanes); DriverAttributes->setAttribute("MaxAuxBuffers", MaxAuxBuffers); DriverAttributes->setAttribute("MaxMultipleRenderTargets", (s32)Feature.MultipleRenderTarget); DriverAttributes->setAttribute("MaxIndices", (s32)MaxIndices); @@ -135,10 +134,6 @@ bool COpenGLDriver::genericDriverInit() glPixelStorei(GL_PACK_ALIGNMENT, 1); - UserClipPlanes.reallocate(MaxUserClipPlanes); - for (i = 0; i < MaxUserClipPlanes; ++i) - UserClipPlanes.push_back(SUserClipPlane()); - for (i = 0; i < ETS_COUNT; ++i) setTransform(static_cast(i), core::IdentityMatrix); @@ -244,11 +239,6 @@ void COpenGLDriver::setTransform(E_TRANSFORMATION_STATE state, const core::matri // first load the viewing transformation for user clip planes glLoadMatrixf((Matrices[ETS_VIEW]).pointer()); - // we have to update the clip planes to the latest view matrix - for (u32 i = 0; i < MaxUserClipPlanes; ++i) - if (UserClipPlanes[i].Enabled) - uploadClipPlane(i); - // now the real model-view matrix glMultMatrixf(Matrices[ETS_WORLD].pointer()); } break; @@ -1597,15 +1587,14 @@ inline void COpenGLDriver::getGLTextureMatrix(GLfloat *o, const core::matrix4 &m ITexture *COpenGLDriver::createDeviceDependentTexture(const io::path &name, IImage *image) { - core::array imageArray(1); - imageArray.push_back(image); + std::vector tmp { image }; - COpenGLTexture *texture = new COpenGLTexture(name, imageArray, ETT_2D, this); + COpenGLTexture *texture = new COpenGLTexture(name, tmp, ETT_2D, this); return texture; } -ITexture *COpenGLDriver::createDeviceDependentTextureCubemap(const io::path &name, const core::array &image) +ITexture *COpenGLDriver::createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) { COpenGLTexture *texture = new COpenGLTexture(name, image, ETT_CUBEMAP, this); @@ -3062,44 +3051,6 @@ IImage *COpenGLDriver::createScreenShot(video::ECOLOR_FORMAT format, video::E_RE return newImage; } -//! Set/unset a clipping plane. -bool COpenGLDriver::setClipPlane(u32 index, const core::plane3df &plane, bool enable) -{ - if (index >= MaxUserClipPlanes) - return false; - - UserClipPlanes[index].Plane = plane; - enableClipPlane(index, enable); - return true; -} - -void COpenGLDriver::uploadClipPlane(u32 index) -{ - // opengl needs an array of doubles for the plane equation - GLdouble clip_plane[4]; - clip_plane[0] = UserClipPlanes[index].Plane.Normal.X; - clip_plane[1] = UserClipPlanes[index].Plane.Normal.Y; - clip_plane[2] = UserClipPlanes[index].Plane.Normal.Z; - clip_plane[3] = UserClipPlanes[index].Plane.D; - glClipPlane(GL_CLIP_PLANE0 + index, clip_plane); -} - -//! Enable/disable a clipping plane. -void COpenGLDriver::enableClipPlane(u32 index, bool enable) -{ - if (index >= MaxUserClipPlanes) - return; - if (enable) { - if (!UserClipPlanes[index].Enabled) { - uploadClipPlane(index); - glEnable(GL_CLIP_PLANE0 + index); - } - } else - glDisable(GL_CLIP_PLANE0 + index); - - UserClipPlanes[index].Enabled = enable; -} - core::dimension2du COpenGLDriver::getMaxTextureSize() const { return core::dimension2du(MaxTextureSize, MaxTextureSize); diff --git a/irr/src/COpenGLDriver.h b/irr/src/COpenGLDriver.h index 6fe8ac37d..3c0a718fc 100644 --- a/irr/src/COpenGLDriver.h +++ b/irr/src/COpenGLDriver.h @@ -285,19 +285,6 @@ public: //! for performance reasons only available in debug mode bool testGLError(int code = 0); - //! Set/unset a clipping plane. - //! There are at least 6 clipping planes available for the user to set at will. - //! \param index: The plane index. Must be between 0 and MaxUserClipPlanes. - //! \param plane: The plane itself. - //! \param enable: If true, enable the clipping plane else disable it. - bool setClipPlane(u32 index, const core::plane3df &plane, bool enable = false) override; - - //! Enable/disable a clipping plane. - //! There are at least 6 clipping planes available for the user to set at will. - //! \param index: The plane index. Must be between 0 and MaxUserClipPlanes. - //! \param enable: If true, enable the clipping plane else disable it. - void enableClipPlane(u32 index, bool enable) override; - //! Enable the 2d override material void enableMaterial2D(bool enable = true) override; @@ -343,14 +330,12 @@ private: bool updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuffer); bool updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffer); - void uploadClipPlane(u32 index); - //! inits the parts of the open gl driver used on all platforms bool genericDriverInit(); ITexture *createDeviceDependentTexture(const io::path &name, IImage *image) override; - ITexture *createDeviceDependentTextureCubemap(const io::path &name, const core::array &image) override; + ITexture *createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) override; //! creates a transposed matrix in supplied GLfloat array to pass to OpenGL inline void getGLMatrix(GLfloat gl_matrix[16], const core::matrix4 &m); @@ -404,15 +389,6 @@ private: SMaterial Material, LastMaterial; - struct SUserClipPlane - { - SUserClipPlane() : - Enabled(false) {} - core::plane3df Plane; - bool Enabled; - }; - core::array UserClipPlanes; - core::stringc VendorName; core::matrix4 TextureFlipMatrix; diff --git a/irr/src/COpenGLExtensionHandler.cpp b/irr/src/COpenGLExtensionHandler.cpp index d5aade126..6127d6e46 100644 --- a/irr/src/COpenGLExtensionHandler.cpp +++ b/irr/src/COpenGLExtensionHandler.cpp @@ -20,7 +20,7 @@ bool COpenGLExtensionHandler::needsDSAFramebufferHack = true; COpenGLExtensionHandler::COpenGLExtensionHandler() : StencilBuffer(false), TextureCompressionExtension(false), MaxLights(1), - MaxAnisotropy(1), MaxUserClipPlanes(0), MaxAuxBuffers(0), MaxIndices(65535), + MaxAnisotropy(1), MaxAuxBuffers(0), MaxIndices(65535), MaxTextureSize(1), MaxGeometryVerticesOut(0), MaxTextureLODBias(0.f), Version(0), ShaderLanguageVersion(0), OcclusionQuerySupport(false), IsAtiRadeonX(false), pGlActiveTexture(0), pGlActiveTextureARB(0), pGlClientActiveTextureARB(0), @@ -428,8 +428,6 @@ void COpenGLExtensionHandler::initExtensions(video::IContextManager *cmgr, bool if (FeatureAvailable[IRR_EXT_texture_lod_bias]) glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS_EXT, &MaxTextureLODBias); #endif - glGetIntegerv(GL_MAX_CLIP_PLANES, &num); - MaxUserClipPlanes = static_cast(num); glGetIntegerv(GL_AUX_BUFFERS, &num); MaxAuxBuffers = static_cast(num); #ifdef GL_ARB_draw_buffers diff --git a/irr/src/COpenGLExtensionHandler.h b/irr/src/COpenGLExtensionHandler.h index ada1c9a3a..cdff911b9 100644 --- a/irr/src/COpenGLExtensionHandler.h +++ b/irr/src/COpenGLExtensionHandler.h @@ -1019,8 +1019,6 @@ public: u8 MaxLights; //! Maximal Anisotropy u8 MaxAnisotropy; - //! Number of user clipplanes - u8 MaxUserClipPlanes; //! Number of auxiliary buffers u8 MaxAuxBuffers; //! Optimal number of indices per meshbuffer diff --git a/irr/src/CSceneManager.cpp b/irr/src/CSceneManager.cpp index b20f6010a..cd88e1a6e 100644 --- a/irr/src/CSceneManager.cpp +++ b/irr/src/CSceneManager.cpp @@ -2,6 +2,8 @@ // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h +#include + #include "CSceneManager.h" #include "IVideoDriver.h" #include "IFileSystem.h" @@ -95,9 +97,8 @@ CSceneManager::~CSceneManager() if (CollisionManager) CollisionManager->drop(); - u32 i; - for (i = 0; i < MeshLoaderList.size(); ++i) - MeshLoaderList[i]->drop(); + for (auto *loader : MeshLoaderList) + loader->drop(); if (ActiveCamera) ActiveCamera->drop(); @@ -140,12 +141,11 @@ IAnimatedMesh *CSceneManager::getUncachedMesh(io::IReadFile *file, const io::pat IAnimatedMesh *msh = 0; // iterate the list in reverse order so user-added loaders can override the built-in ones - s32 count = MeshLoaderList.size(); - for (s32 i = count - 1; i >= 0; --i) { - if (MeshLoaderList[i]->isALoadableFileExtension(filename)) { + for (auto it = MeshLoaderList.rbegin(); it != MeshLoaderList.rend(); it++) { + if ((*it)->isALoadableFileExtension(filename)) { // reset file to avoid side effects of previous calls to createMesh file->seek(0); - msh = MeshLoaderList[i]->createMesh(file); + msh = (*it)->createMesh(file); if (msh) { MeshCache->addMesh(cachename, msh); msh->drop(); @@ -388,14 +388,8 @@ u32 CSceneManager::registerNodeForRendering(ISceneNode *node, E_SCENE_NODE_RENDE switch (pass) { // take camera if it is not already registered case ESNRP_CAMERA: { - taken = 1; - for (u32 i = 0; i != CameraList.size(); ++i) { - if (CameraList[i] == node) { - taken = 0; - break; - } - } - if (taken) { + if (std::find(CameraList.begin(), CameraList.end(), node) == CameraList.end()) { + taken = 1; CameraList.push_back(node); } } break; @@ -405,19 +399,19 @@ u32 CSceneManager::registerNodeForRendering(ISceneNode *node, E_SCENE_NODE_RENDE break; case ESNRP_SOLID: if (!isCulled(node)) { - SolidNodeList.push_back(node); + SolidNodeList.emplace_back(node); taken = 1; } break; case ESNRP_TRANSPARENT: if (!isCulled(node)) { - TransparentNodeList.push_back(TransparentNodeEntry(node, camWorldPos)); + TransparentNodeList.emplace_back(node, camWorldPos); taken = 1; } break; case ESNRP_TRANSPARENT_EFFECT: if (!isCulled(node)) { - TransparentEffectNodeList.push_back(TransparentNodeEntry(node, camWorldPos)); + TransparentEffectNodeList.emplace_back(node, camWorldPos); taken = 1; } break; @@ -429,8 +423,7 @@ u32 CSceneManager::registerNodeForRendering(ISceneNode *node, E_SCENE_NODE_RENDE for (u32 i = 0; i < count; ++i) { if (Driver->needsTransparentRenderPass(node->getMaterial(i))) { // register as transparent node - TransparentNodeEntry e(node, camWorldPos); - TransparentNodeList.push_back(e); + TransparentNodeList.emplace_back(node, camWorldPos); taken = 1; break; } @@ -438,7 +431,7 @@ u32 CSceneManager::registerNodeForRendering(ISceneNode *node, E_SCENE_NODE_RENDE // not transparent, register as solid if (!taken) { - SolidNodeList.push_back(node); + SolidNodeList.emplace_back(node); taken = 1; } } @@ -509,10 +502,10 @@ void CSceneManager::drawAll() CurrentRenderPass = ESNRP_CAMERA; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); - for (i = 0; i < CameraList.size(); ++i) - CameraList[i]->render(); + for (auto *node : CameraList) + node->render(); - CameraList.set_used(0); + CameraList.clear(); } // render skyboxes @@ -520,10 +513,10 @@ void CSceneManager::drawAll() CurrentRenderPass = ESNRP_SKY_BOX; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); - for (i = 0; i < SkyBoxList.size(); ++i) - SkyBoxList[i]->render(); + for (auto *node : SkyBoxList) + node->render(); - SkyBoxList.set_used(0); + SkyBoxList.clear(); } // render default objects @@ -531,12 +524,12 @@ void CSceneManager::drawAll() CurrentRenderPass = ESNRP_SOLID; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); - SolidNodeList.sort(); // sort by textures + std::sort(SolidNodeList.begin(), SolidNodeList.end()); - for (i = 0; i < SolidNodeList.size(); ++i) - SolidNodeList[i].Node->render(); + for (auto &it : SolidNodeList) + it.Node->render(); - SolidNodeList.set_used(0); + SolidNodeList.clear(); } // render transparent objects. @@ -544,11 +537,12 @@ void CSceneManager::drawAll() CurrentRenderPass = ESNRP_TRANSPARENT; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); - TransparentNodeList.sort(); // sort by distance from camera - for (i = 0; i < TransparentNodeList.size(); ++i) - TransparentNodeList[i].Node->render(); + std::sort(TransparentNodeList.begin(), TransparentNodeList.end()); - TransparentNodeList.set_used(0); + for (auto &it : TransparentNodeList) + it.Node->render(); + + TransparentNodeList.clear(); } // render transparent effect objects. @@ -556,12 +550,12 @@ void CSceneManager::drawAll() CurrentRenderPass = ESNRP_TRANSPARENT_EFFECT; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); - TransparentEffectNodeList.sort(); // sort by distance from camera + std::sort(TransparentEffectNodeList.begin(), TransparentEffectNodeList.end()); - for (i = 0; i < TransparentEffectNodeList.size(); ++i) - TransparentEffectNodeList[i].Node->render(); + for (auto &it : TransparentEffectNodeList) + it.Node->render(); - TransparentEffectNodeList.set_used(0); + TransparentEffectNodeList.clear(); } // render custom gui nodes @@ -569,10 +563,10 @@ void CSceneManager::drawAll() CurrentRenderPass = ESNRP_GUI; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); - for (i = 0; i < GuiNodeList.size(); ++i) - GuiNodeList[i]->render(); + for (auto *node : GuiNodeList) + node->render(); - GuiNodeList.set_used(0); + GuiNodeList.clear(); } clearDeletionList(); @@ -592,7 +586,7 @@ void CSceneManager::addExternalMeshLoader(IMeshLoader *externalLoader) //! Returns the number of mesh loaders supported by Irrlicht at this time u32 CSceneManager::getMeshLoaderCount() const { - return MeshLoaderList.size(); + return static_cast(MeshLoaderList.size()); } //! Retrieve the given mesh loader @@ -629,12 +623,9 @@ void CSceneManager::addToDeletionQueue(ISceneNode *node) //! clears the deletion list void CSceneManager::clearDeletionList() { - if (DeletionList.empty()) - return; - - for (u32 i = 0; i < DeletionList.size(); ++i) { - DeletionList[i]->remove(); - DeletionList[i]->drop(); + for (auto *node : DeletionList) { + node->remove(); + node->drop(); } DeletionList.clear(); diff --git a/irr/src/CSceneManager.h b/irr/src/CSceneManager.h index 0e27290a8..32df145ec 100644 --- a/irr/src/CSceneManager.h +++ b/irr/src/CSceneManager.h @@ -277,15 +277,15 @@ private: ISceneCollisionManager *CollisionManager; //! render pass lists - core::array CameraList; - core::array SkyBoxList; - core::array SolidNodeList; - core::array TransparentNodeList; - core::array TransparentEffectNodeList; - core::array GuiNodeList; + std::vector CameraList; + std::vector SkyBoxList; + std::vector SolidNodeList; + std::vector TransparentNodeList; + std::vector TransparentEffectNodeList; + std::vector GuiNodeList; - core::array MeshLoaderList; - core::array DeletionList; + std::vector MeshLoaderList; + std::vector DeletionList; //! current active camera ICameraSceneNode *ActiveCamera; diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index 5978980f4..4e11fe352 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -273,12 +273,12 @@ bool CXMeshFileLoader::load(io::IReadFile *file) } if (mesh->TCoords2.size()) { for (i = 0; i != mesh->Buffers.size(); ++i) { - mesh->Buffers[i]->Vertices_2TCoords.reallocate(vCountArray[i]); + mesh->Buffers[i]->Vertices_2TCoords.reserve(vCountArray[i]); mesh->Buffers[i]->VertexType = video::EVT_2TCOORDS; } } else { for (i = 0; i != mesh->Buffers.size(); ++i) - mesh->Buffers[i]->Vertices_Standard.reallocate(vCountArray[i]); + mesh->Buffers[i]->Vertices_Standard.reserve(vCountArray[i]); } verticesLinkIndex.set_used(mesh->Vertices.size()); @@ -291,10 +291,10 @@ bool CXMeshFileLoader::load(io::IReadFile *file) if (mesh->TCoords2.size()) { verticesLinkIndex[i] = buffer->Vertices_2TCoords.size(); - buffer->Vertices_2TCoords.push_back(mesh->Vertices[i]); + buffer->Vertices_2TCoords.emplace_back(mesh->Vertices[i]); // We have a problem with correct tcoord2 handling here // crash fixed for now by checking the values - buffer->Vertices_2TCoords.getLast().TCoords2 = (i < mesh->TCoords2.size()) ? mesh->TCoords2[i] : mesh->Vertices[i].TCoords; + buffer->Vertices_2TCoords.back().TCoords2 = (i < mesh->TCoords2.size()) ? mesh->TCoords2[i] : mesh->Vertices[i].TCoords; } else { verticesLinkIndex[i] = buffer->Vertices_Standard.size(); buffer->Vertices_Standard.push_back(mesh->Vertices[i]); @@ -306,7 +306,7 @@ bool CXMeshFileLoader::load(io::IReadFile *file) for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) ++vCountArray[mesh->FaceMaterialIndices[i]]; for (i = 0; i != mesh->Buffers.size(); ++i) - mesh->Buffers[i]->Indices.reallocate(vCountArray[i]); + mesh->Buffers[i]->Indices.reserve(vCountArray[i]); delete[] vCountArray; // create indices per buffer for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) { diff --git a/irr/src/CZipReader.cpp b/irr/src/CZipReader.cpp index b761c72e8..2d2152719 100644 --- a/irr/src/CZipReader.cpp +++ b/irr/src/CZipReader.cpp @@ -325,7 +325,7 @@ bool CZipReader::scanZipHeader(bool ignoreGPBits) dirEnd.Offset = os::Byteswap::byteswap(dirEnd.Offset); dirEnd.CommentLength = os::Byteswap::byteswap(dirEnd.CommentLength); #endif - FileInfo.reallocate(dirEnd.TotalEntries); + FileInfo.reserve(dirEnd.TotalEntries); File->seek(dirEnd.Offset); while (scanCentralDirectoryHeader()) { } @@ -381,9 +381,10 @@ bool CZipReader::scanCentralDirectoryHeader() File->seek(entry.RelativeOffsetOfLocalHeader); scanZipHeader(true); File->seek(pos + entry.FilenameLength + entry.ExtraFieldLength + entry.FileCommentLength); - FileInfo.getLast().header.DataDescriptor.CompressedSize = entry.CompressedSize; - FileInfo.getLast().header.DataDescriptor.UncompressedSize = entry.UncompressedSize; - FileInfo.getLast().header.DataDescriptor.CRC32 = entry.CRC32; + auto &lastInfo = FileInfo.back(); + lastInfo.header.DataDescriptor.CompressedSize = entry.CompressedSize; + lastInfo.header.DataDescriptor.UncompressedSize = entry.UncompressedSize; + lastInfo.header.DataDescriptor.CRC32 = entry.CRC32; Files.getLast().Size = entry.UncompressedSize; return true; } diff --git a/irr/src/CZipReader.h b/irr/src/CZipReader.h index d9afd668a..b520c2030 100644 --- a/irr/src/CZipReader.h +++ b/irr/src/CZipReader.h @@ -4,8 +4,8 @@ #pragma once +#include #include "IReadFile.h" -#include "irrArray.h" #include "irrString.h" #include "IFileSystem.h" #include "CFileList.h" @@ -209,7 +209,7 @@ protected: IReadFile *File; // holds extended info about files - core::array FileInfo; + std::vector FileInfo; bool IsGZip; }; diff --git a/irr/src/IAttribute.h b/irr/src/IAttribute.h index b0dc76eee..23352a623 100644 --- a/irr/src/IAttribute.h +++ b/irr/src/IAttribute.h @@ -4,15 +4,8 @@ #pragma once -#include "IReferenceCounted.h" -#include "SColor.h" -#include "vector3d.h" -#include "vector2d.h" -#include "position2d.h" -#include "rect.h" -#include "dimension2d.h" +#include "irrTypes.h" #include "irrString.h" -#include "irrArray.h" #include "EAttributes.h" namespace irr @@ -20,16 +13,7 @@ namespace irr namespace io { -// All derived attribute types implement at least getter/setter for their own type (like CBoolAttribute will have setBool/getBool). -// Simple types will also implement getStringW and setString, but don't expect it to work for all types. -// String serialization makes no sense for some attribute-types (like stringw arrays or pointers), but is still useful for many types. -// (Note: I do _not_ know yet why the default string serialization is asymmetric with char* in set and wchar_t* in get). -// Additionally many attribute types will implement conversion functions like CBoolAttribute has p.E. getInt/setInt(). -// The reason for conversion functions is likely to make reading old formats easier which have changed in the meantime. For example -// an old xml can contain a bool attribute which is an int in a newer format. You can still call getInt() even thought the attribute has the wrong type. -// And please do _not_ confuse these attributes here with the ones used in the xml-reader (aka SAttribute which is just a key-value pair). - -class IAttribute : public virtual IReferenceCounted +class IAttribute { public: virtual ~IAttribute(){}; diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 95d760548..1defa9abc 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -257,7 +257,6 @@ bool COpenGL3DriverBase::genericDriverInit(const core::dimension2d &screenS DriverAttributes->setAttribute("MaxSupportedTextures", (s32)Feature.MaxTextureUnits); // DriverAttributes->setAttribute("MaxLights", MaxLights); DriverAttributes->setAttribute("MaxAnisotropy", MaxAnisotropy); - // DriverAttributes->setAttribute("MaxUserClipPlanes", MaxUserClipPlanes); // DriverAttributes->setAttribute("MaxAuxBuffers", MaxAuxBuffers); // DriverAttributes->setAttribute("MaxMultipleRenderTargets", MaxMultipleRenderTargets); DriverAttributes->setAttribute("MaxIndices", (s32)MaxIndices); @@ -268,8 +267,6 @@ bool COpenGL3DriverBase::genericDriverInit(const core::dimension2d &screenS GL.PixelStorei(GL_PACK_ALIGNMENT, 1); - UserClipPlane.reallocate(0); - for (s32 i = 0; i < ETS_COUNT; ++i) setTransform(static_cast(i), core::IdentityMatrix); @@ -916,7 +913,8 @@ void COpenGL3DriverBase::draw2DImageBatch(const video::ITexture *texture, const irr::u32 drawCount = core::min_(positions.size(), sourceRects.size()); assert(6 * drawCount <= QuadIndexCount); // FIXME split the batch? or let it crash? - core::array vtx(drawCount * 4); + std::vector vtx; + vtx.reserve(drawCount * 4); for (u32 i = 0; i < drawCount; i++) { core::position2d targetPos = positions[i]; @@ -939,22 +937,22 @@ void COpenGL3DriverBase::draw2DImageBatch(const video::ITexture *texture, f32 down = 2.f - (f32)poss.LowerRightCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; f32 top = 2.f - (f32)poss.UpperLeftCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; - vtx.push_back(S3DVertex(left, top, 0.0f, + vtx.emplace_back(left, top, 0.0f, 0.0f, 0.0f, 0.0f, color, - tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y)); - vtx.push_back(S3DVertex(right, top, 0.0f, + tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y); + vtx.emplace_back(right, top, 0.0f, 0.0f, 0.0f, 0.0f, color, - tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y)); - vtx.push_back(S3DVertex(right, down, 0.0f, + tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y); + vtx.emplace_back(right, down, 0.0f, 0.0f, 0.0f, 0.0f, color, - tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y)); - vtx.push_back(S3DVertex(left, down, 0.0f, + tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y); + vtx.emplace_back(left, down, 0.0f, 0.0f, 0.0f, 0.0f, color, - tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y)); + tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y); } GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, QuadIndexBuffer); - drawElements(GL_TRIANGLES, vt2DImage, vtx.const_pointer(), vtx.size(), 0, 6 * drawCount); + drawElements(GL_TRIANGLES, vt2DImage, vtx.data(), vtx.size(), 0, 6 * drawCount); GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); if (clipRect) @@ -1104,15 +1102,14 @@ void COpenGL3DriverBase::endDraw(const VertexType &vertexType) ITexture *COpenGL3DriverBase::createDeviceDependentTexture(const io::path &name, IImage *image) { - core::array imageArray(1); - imageArray.push_back(image); + std::vector tmp { image }; - COpenGL3Texture *texture = new COpenGL3Texture(name, imageArray, ETT_2D, this); + COpenGL3Texture *texture = new COpenGL3Texture(name, tmp, ETT_2D, this); return texture; } -ITexture *COpenGL3DriverBase::createDeviceDependentTextureCubemap(const io::path &name, const core::array &image) +ITexture *COpenGL3DriverBase::createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) { COpenGL3Texture *texture = new COpenGL3Texture(name, image, ETT_CUBEMAP, this); @@ -1876,40 +1873,6 @@ void COpenGL3DriverBase::removeTexture(ITexture *texture) CNullDriver::removeTexture(texture); } -//! Set/unset a clipping plane. -bool COpenGL3DriverBase::setClipPlane(u32 index, const core::plane3df &plane, bool enable) -{ - if (index >= UserClipPlane.size()) - UserClipPlane.push_back(SUserClipPlane()); - - UserClipPlane[index].Plane = plane; - UserClipPlane[index].Enabled = enable; - return true; -} - -//! Enable/disable a clipping plane. -void COpenGL3DriverBase::enableClipPlane(u32 index, bool enable) -{ - UserClipPlane[index].Enabled = enable; -} - -//! Get the ClipPlane Count -u32 COpenGL3DriverBase::getClipPlaneCount() const -{ - return UserClipPlane.size(); -} - -const core::plane3df &COpenGL3DriverBase::getClipPlane(irr::u32 index) const -{ - if (index < UserClipPlane.size()) - return UserClipPlane[index].Plane; - else { - _IRR_DEBUG_BREAK_IF(true) // invalid index - static const core::plane3df dummy; - return dummy; - } -} - core::dimension2du COpenGL3DriverBase::getMaxTextureSize() const { return core::dimension2du(MaxTextureSize, MaxTextureSize); diff --git a/irr/src/OpenGL/Driver.h b/irr/src/OpenGL/Driver.h index 51554334b..be4e4db9e 100644 --- a/irr/src/OpenGL/Driver.h +++ b/irr/src/OpenGL/Driver.h @@ -7,7 +7,6 @@ #pragma once #include "SIrrCreationParameters.h" - #include "Common.h" #include "CNullDriver.h" #include "IMaterialRendererServices.h" @@ -227,18 +226,6 @@ public: // Does *nothing* unless in debug mode. bool testGLError(const char *file, int line); - //! Set/unset a clipping plane. - bool setClipPlane(u32 index, const core::plane3df &plane, bool enable = false) override; - - //! returns the current amount of user clip planes set. - u32 getClipPlaneCount() const; - - //! returns the 0 indexed Plane - const core::plane3df &getClipPlane(u32 index) const; - - //! Enable/disable a clipping plane. - void enableClipPlane(u32 index, bool enable) override; - //! Returns the graphics card vendor name. core::stringc getVendorInfo() override { @@ -278,7 +265,7 @@ protected: ITexture *createDeviceDependentTexture(const io::path &name, IImage *image) override; - ITexture *createDeviceDependentTextureCubemap(const io::path &name, const core::array &image) override; + ITexture *createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) override; //! Map Irrlicht wrap mode to OpenGL enum GLint getTextureWrapMode(u8 clamp) const; @@ -337,14 +324,6 @@ protected: bool LockRenderStateMode; u8 AntiAlias; - struct SUserClipPlane - { - core::plane3df Plane; - bool Enabled; - }; - - core::array UserClipPlane; - core::matrix4 TextureFlipMatrix; using FColorConverter = void (*)(const void *source, s32 count, void *dest); diff --git a/irr/src/OpenGL/MaterialRenderer.cpp b/irr/src/OpenGL/MaterialRenderer.cpp index 51a796d48..3c32ba318 100644 --- a/irr/src/OpenGL/MaterialRenderer.cpp +++ b/irr/src/OpenGL/MaterialRenderer.cpp @@ -262,35 +262,30 @@ bool COpenGL3MaterialRenderer::linkProgram() // seems that some implementations use an extra null terminator. ++maxlen; - c8 *buf = new c8[maxlen]; + std::vector buf(maxlen); UniformInfo.clear(); - UniformInfo.reallocate(num); + UniformInfo.reserve(num); for (GLint i = 0; i < num; ++i) { SUniformInfo ui; - memset(buf, 0, maxlen); + memset(buf.data(), 0, buf.size()); GLint size; - GL.GetActiveUniform(Program, i, maxlen, 0, &size, &ui.type, reinterpret_cast(buf)); - - core::stringc name = ""; + GL.GetActiveUniform(Program, i, maxlen, 0, &size, &ui.type, reinterpret_cast(buf.data())); // array support, workaround for some bugged drivers. for (s32 i = 0; i < maxlen; ++i) { if (buf[i] == '[' || buf[i] == '\0') break; - name += buf[i]; + ui.name += buf[i]; } - ui.name = name; - ui.location = GL.GetUniformLocation(Program, buf); + ui.location = GL.GetUniformLocation(Program, buf.data()); - UniformInfo.push_back(ui); + UniformInfo.push_back(std::move(ui)); } - - delete[] buf; } return true; diff --git a/irr/src/OpenGL/MaterialRenderer.h b/irr/src/OpenGL/MaterialRenderer.h index fa37cfff3..fca71478a 100644 --- a/irr/src/OpenGL/MaterialRenderer.h +++ b/irr/src/OpenGL/MaterialRenderer.h @@ -4,12 +4,12 @@ #pragma once +#include +#include #include "EMaterialTypes.h" #include "IMaterialRenderer.h" #include "IMaterialRendererServices.h" #include "IGPUProgrammingServices.h" -#include "irrArray.h" -#include "irrString.h" #include "Common.h" @@ -79,13 +79,13 @@ protected: struct SUniformInfo { - core::stringc name; + std::string name; GLenum type; GLint location; }; GLuint Program; - core::array UniformInfo; + std::vector UniformInfo; s32 UserData; }; diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index d53d52957..ebb8b9000 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -184,15 +184,15 @@ void Clouds::updateMesh() const u32 index_count = quad_count * 6; // reserve memory - mb->Vertices.reallocate(vertex_count); - mb->Indices.reallocate(index_count); + mb->Vertices.reserve(vertex_count); + mb->Indices.reserve(index_count); } #define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius)) #define INAREA(x, z, radius) \ ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius)) - mb->Vertices.set_used(0); + mb->Vertices.clear(); for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++) for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++) { @@ -322,7 +322,7 @@ void Clouds::updateMesh() const u32 index_count = quad_count * 6; // rewrite index array as needed if (mb->getIndexCount() > index_count) { - mb->Indices.set_used(index_count); + mb->Indices.resize(index_count); mb->setDirty(scene::EBT_INDEX); } else if (mb->getIndexCount() < index_count) { const u32 start = mb->getIndexCount() / 6; diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 3a0e25a07..962818b8f 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -119,27 +119,28 @@ Hud::Hud(Client *client, LocalPlayer *player, } // Prepare mesh for compass drawing - m_rotation_mesh_buffer.Vertices.set_used(4); - m_rotation_mesh_buffer.Indices.set_used(6); + auto &b = m_rotation_mesh_buffer; + b.Vertices.resize(4); + b.Indices.resize(6); video::SColor white(255, 255, 255, 255); v3f normal(0.f, 0.f, 1.f); - m_rotation_mesh_buffer.Vertices[0] = video::S3DVertex(v3f(-1.f, -1.f, 0.f), normal, white, v2f(0.f, 1.f)); - m_rotation_mesh_buffer.Vertices[1] = video::S3DVertex(v3f(-1.f, 1.f, 0.f), normal, white, v2f(0.f, 0.f)); - m_rotation_mesh_buffer.Vertices[2] = video::S3DVertex(v3f( 1.f, 1.f, 0.f), normal, white, v2f(1.f, 0.f)); - m_rotation_mesh_buffer.Vertices[3] = video::S3DVertex(v3f( 1.f, -1.f, 0.f), normal, white, v2f(1.f, 1.f)); + b.Vertices[0] = video::S3DVertex(v3f(-1.f, -1.f, 0.f), normal, white, v2f(0.f, 1.f)); + b.Vertices[1] = video::S3DVertex(v3f(-1.f, 1.f, 0.f), normal, white, v2f(0.f, 0.f)); + b.Vertices[2] = video::S3DVertex(v3f( 1.f, 1.f, 0.f), normal, white, v2f(1.f, 0.f)); + b.Vertices[3] = video::S3DVertex(v3f( 1.f, -1.f, 0.f), normal, white, v2f(1.f, 1.f)); - m_rotation_mesh_buffer.Indices[0] = 0; - m_rotation_mesh_buffer.Indices[1] = 1; - m_rotation_mesh_buffer.Indices[2] = 2; - m_rotation_mesh_buffer.Indices[3] = 2; - m_rotation_mesh_buffer.Indices[4] = 3; - m_rotation_mesh_buffer.Indices[5] = 0; + b.Indices[0] = 0; + b.Indices[1] = 1; + b.Indices[2] = 2; + b.Indices[3] = 2; + b.Indices[4] = 3; + b.Indices[5] = 0; - m_rotation_mesh_buffer.getMaterial().Lighting = false; - m_rotation_mesh_buffer.getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - m_rotation_mesh_buffer.setHardwareMappingHint(scene::EHM_STATIC); + b.getMaterial().Lighting = false; + b.getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + b.setHardwareMappingHint(scene::EHM_STATIC); } void Hud::readScalingSetting() diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 429464e04..9b5612148 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -602,7 +602,7 @@ void PartialMeshBuffer::beforeDraw() const void PartialMeshBuffer::afterDraw() const { // Take the data back - m_vertex_indexes = m_buffer->Indices.steal(); + m_vertex_indexes = std::move(m_buffer->Indices); } /* diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index afac89843..9f1f04359 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -555,8 +555,8 @@ v3f Minimap::getYawVec() scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() { scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Vertices.set_used(4); - buf->Indices.set_used(6); + buf->Vertices.resize(4); + buf->Indices.resize(6); static const video::SColor c(255, 255, 255, 255); buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 92e5df218..c68d13984 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -830,8 +830,8 @@ void Sky::updateStars() warningstream << "Requested " << m_star_params.count << " stars but " << 0x4000 << " is the max\n"; m_star_params.count = 0x4000; } - m_stars->Vertices.reallocate(4 * m_star_params.count); - m_stars->Indices.reallocate(6 * m_star_params.count); + m_stars->Vertices.reserve(4 * m_star_params.count); + m_stars->Indices.reserve(6 * m_star_params.count); video::SColor fallback_color = m_star_params.starcolor; // used on GLES 2 “without shaders” PcgRandom rgen(m_seed); From 3df070f352520c3678e509f7c096977b62d2e3bf Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 17 Aug 2024 09:34:22 -0700 Subject: [PATCH 006/200] Remove SAO::onAttach() and SAO::onDetach() --- src/server/serveractiveobject.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/server/serveractiveobject.h b/src/server/serveractiveobject.h index d734b8469..486b3b230 100644 --- a/src/server/serveractiveobject.h +++ b/src/server/serveractiveobject.h @@ -259,9 +259,6 @@ protected: virtual void onMarkedForDeactivation() {} virtual void onMarkedForRemoval() {} - virtual void onAttach(object_t parent_id) {} - virtual void onDetach(object_t parent_id) {} - ServerEnvironment *m_env; v3f m_base_position; std::unordered_set m_attached_particle_spawners; From c65444c43b3aa446a57aa72bb887b83441925aad Mon Sep 17 00:00:00 2001 From: cx384 Date: Fri, 16 Aug 2024 11:53:16 +0200 Subject: [PATCH 007/200] Add whitespace checks to ci --- .github/workflows/whitespace_checks.yml | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/whitespace_checks.yml diff --git a/.github/workflows/whitespace_checks.yml b/.github/workflows/whitespace_checks.yml new file mode 100644 index 000000000..cc16e7b22 --- /dev/null +++ b/.github/workflows/whitespace_checks.yml @@ -0,0 +1,45 @@ +name: whitespace_checks + +# Check whitespaces of the following file types +# Not checked: .lua, .yml, .properties, .conf, .java, .py, .svg, .gradle, .xml, ... +# (luacheck already checks .lua files) +on: + push: + paths: + - '**.txt' + - '**.md' + - '**.[ch]' + - '**.cpp' + - '**.hpp' + - '**.sh' + - '**.cmake' + - '**.glsl' + pull_request: + paths: + - '**.txt' + - '**.md' + - '**.[ch]' + - '**.cpp' + - '**.hpp' + - '**.sh' + - '**.cmake' + - '**.glsl' + +jobs: + trailing_whitespaces: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Line endings are already ensured by .gitattributes + - name: Check trailing whitespaces + run: if git ls-files | grep -E '\.txt$|\.md$|\.[ch]$|\.cpp$|\.hpp$|\.sh$|\.cmake$|\.glsl$' | xargs grep -n '\s$'; then echo -e "\033[0;31mFound trailing whitespace"; (exit 1); else (exit 0); fi + + tabs_lua_api_files: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Some files should not contain tabs + - name: Check tabs in Lua API files + run: if grep -n $'\t' doc/lua_api.md doc/client_lua_api.md; then echo -e "\033[0;31mFound tab in markdown file"; (exit 1); else (exit 0); fi + + From 03e600a721c79d5cdce699123e698cd380abcf1c Mon Sep 17 00:00:00 2001 From: cx384 Date: Fri, 16 Aug 2024 11:53:31 +0200 Subject: [PATCH 008/200] Fix whitespaces --- client/shaders/fxaa/opengl_fragment.glsl | 8 ++-- client/shaders/fxaa/opengl_vertex.glsl | 2 +- doc/client_lua_api.md | 18 ++++---- doc/lua_api.md | 48 ++++++++++----------- doc/world_format.md | 2 +- irr/README.md | 2 +- lib/gmp/mini-gmp.c | 38 ++++++++-------- src/client/game.cpp | 2 +- src/client/mesh.h | 2 +- src/client/shadows/dynamicshadowsrender.cpp | 2 +- src/script/lua_api/l_noise.cpp | 4 +- src/server/activeobjectmgr.cpp | 2 +- src/servermap.cpp | 2 +- 13 files changed, 66 insertions(+), 66 deletions(-) diff --git a/client/shaders/fxaa/opengl_fragment.glsl b/client/shaders/fxaa/opengl_fragment.glsl index 130e689ea..f70064b6d 100644 --- a/client/shaders/fxaa/opengl_fragment.glsl +++ b/client/shaders/fxaa/opengl_fragment.glsl @@ -58,11 +58,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define FXAA_SPAN_MAX 8.0 #endif -//optimized version for mobile, where dependent +//optimized version for mobile, where dependent //texture reads can be a bottleneck vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 inverseVP, - vec2 v_rgbNW, vec2 v_rgbNE, - vec2 v_rgbSW, vec2 v_rgbSE, + vec2 v_rgbNW, vec2 v_rgbNE, + vec2 v_rgbSW, vec2 v_rgbSE, vec2 v_rgbM) { vec4 color; vec3 rgbNW = texture2D(tex, v_rgbNW).xyz; @@ -111,6 +111,6 @@ void main(void) { vec2 uv = varTexCoord.st; - gl_FragColor = fxaa(rendered, uv, texelSize0, + gl_FragColor = fxaa(rendered, uv, texelSize0, sampleNW, sampleNE, sampleSW, sampleSE, uv); } diff --git a/client/shaders/fxaa/opengl_vertex.glsl b/client/shaders/fxaa/opengl_vertex.glsl index 26913c28e..68bb36d1c 100644 --- a/client/shaders/fxaa/opengl_vertex.glsl +++ b/client/shaders/fxaa/opengl_vertex.glsl @@ -12,7 +12,7 @@ varying vec2 sampleSW; varying vec2 sampleSE; /* -Based on +Based on https://github.com/mattdesl/glsl-fxaa/ Portions Copyright (c) 2011 by Armin Ronacher. */ diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index fac3f7b93..08d0317ab 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -315,7 +315,7 @@ Call these functions only at load time! * `minetest.register_globalstep(function(dtime))` * Called every client environment step - * `dtime` is the time since last execution in seconds. + * `dtime` is the time since last execution in seconds. * `minetest.register_on_mods_loaded(function())` * Called just after mods have finished loading. * `minetest.register_on_shutdown(function())` @@ -586,9 +586,9 @@ Call these functions only at load time! * `minetest.camera` * Reference to the camera object. See [`Camera`](#camera) class reference for methods. * `minetest.show_formspec(formname, formspec)` : returns true on success - * Shows a formspec to the player + * Shows a formspec to the player * `minetest.display_chat_message(message)` returns true on success - * Shows a chat message to the current player. + * Shows a chat message to the current player. Setting-related --------------- @@ -866,9 +866,9 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or ----------------- ### Definitions * `minetest.get_node_def(nodename)` - * Returns [node definition](#node-definition) table of `nodename` + * Returns [node definition](#node-definition) table of `nodename` * `minetest.get_item_def(itemstring)` - * Returns item definition table of `itemstring` + * Returns item definition table of `itemstring` #### Node Definition @@ -971,10 +971,10 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or ```lua { - address = "minetest.example.org", -- The domain name/IP address of a remote server or "" for a local server. - ip = "203.0.113.156", -- The IP address of the server. - port = 30000, -- The port the client is connected to. - protocol_version = 30 -- Will not be accurate at start up as the client might not be connected to the server yet, in that case it will be 0. + address = "minetest.example.org", -- The domain name/IP address of a remote server or "" for a local server. + ip = "203.0.113.156", -- The IP address of the server. + port = 30000, -- The port the client is connected to. + protocol_version = 30 -- Will not be accurate at start up as the client might not be connected to the server yet, in that case it will be 0. } ``` diff --git a/doc/lua_api.md b/doc/lua_api.md index 6d486b56b..b37959eed 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -3658,7 +3658,7 @@ Player Inventory lists * `hand`: list containing an override for the empty hand * Is not created automatically, use `InvRef:set_size` * Is only used to enhance the empty hand's tool capabilities - + Custom lists can be added and deleted with `InvRef:set_size(name, size)` like any other inventory. @@ -3854,8 +3854,8 @@ vectors are written like this: `(x, y, z)`: * Returns a vector where the function `func` has been applied to each component. * `vector.combine(v, w, func)`: - * Returns a vector where the function `func` has combined both components of `v` and `w` - for each component + * Returns a vector where the function `func` has combined both components of `v` and `w` + for each component * `vector.equals(v1, v2)`: * Returns a boolean, `true` if the vectors are identical. * `vector.sort(v1, v2)`: @@ -3873,10 +3873,10 @@ vectors are written like this: `(x, y, z)`: by a `vector.*` function. * Returns `false` for anything else, including tables like `{x=3,y=1,z=4}`. * `vector.in_area(pos, min, max)`: - * Returns a boolean value indicating if `pos` is inside area formed by `min` and `max`. - * `min` and `max` are inclusive. - * If `min` is bigger than `max` on some axis, function always returns false. - * You can use `vector.sort` if you have two vectors and don't know which are the minimum and the maximum. + * Returns a boolean value indicating if `pos` is inside area formed by `min` and `max`. + * `min` and `max` are inclusive. + * If `min` is bigger than `max` on some axis, function always returns false. + * You can use `vector.sort` if you have two vectors and don't know which are the minimum and the maximum. For the following functions `x` can be either a vector or a number: @@ -5076,12 +5076,12 @@ Callbacks: used for updating the entity state. * `on_deactivate(self, removal)` * Called when the object is about to get removed or unloaded. - * `removal`: boolean indicating whether the object is about to get removed. - Calling `object:remove()` on an active object will call this with `removal=true`. - The mapblock the entity resides in being unloaded will call this with `removal=false`. - * Note that this won't be called if the object hasn't been activated in the first place. - In particular, `minetest.clear_objects({mode = "full"})` won't call this, - whereas `minetest.clear_objects({mode = "quick"})` might call this. + * `removal`: boolean indicating whether the object is about to get removed. + Calling `object:remove()` on an active object will call this with `removal=true`. + The mapblock the entity resides in being unloaded will call this with `removal=false`. + * Note that this won't be called if the object hasn't been activated in the first place. + In particular, `minetest.clear_objects({mode = "full"})` won't call this, + whereas `minetest.clear_objects({mode = "quick"})` might call this. * `on_step(self, dtime, moveresult)` * Called on every server tick, after movement and collision processing. * `dtime`: elapsed time since last call @@ -5723,7 +5723,7 @@ Call these functions only at load time! * `minetest.register_globalstep(function(dtime))` * Called every server step, usually interval of 0.1s. - * `dtime` is the time since last execution in seconds. + * `dtime` is the time since last execution in seconds. * `minetest.register_on_mods_loaded(function())` * Called after mods have finished loading and before the media is cached or the aliases handled. @@ -6151,7 +6151,7 @@ Environment access * **Warning**: The same warning as for `minetest.get_objects_inside_radius` applies. Use `minetest.objects_in_area` instead to iterate only valid objects. * `minetest.objects_in_area(min_pos, max_pos)` - * returns an iterator of valid objects + * returns an iterator of valid objects * `minetest.set_timeofday(val)`: set time of day * `val` is between `0` and `1`; `0` for midnight, `0.5` for midday * `minetest.get_timeofday()`: get time of day @@ -7961,13 +7961,13 @@ child will follow movement and rotation of that bone. object. * `set_detach()`: Detaches object. No-op if object was not attached. * `set_bone_position([bone, position, rotation])` - * Shorthand for `set_bone_override(bone, {position = position, rotation = rotation:apply(math.rad)})` using absolute values. - * **Note:** Rotation is in degrees, not radians. - * **Deprecated:** Use `set_bone_override` instead. + * Shorthand for `set_bone_override(bone, {position = position, rotation = rotation:apply(math.rad)})` using absolute values. + * **Note:** Rotation is in degrees, not radians. + * **Deprecated:** Use `set_bone_override` instead. * `get_bone_position(bone)`: returns the previously set position and rotation of the bone - * Shorthand for `get_bone_override(bone).position.vec, get_bone_override(bone).rotation.vec:apply(math.deg)`. - * **Note:** Returned rotation is in degrees, not radians. - * **Deprecated:** Use `get_bone_override` instead. + * Shorthand for `get_bone_override(bone).position.vec, get_bone_override(bone).rotation.vec:apply(math.deg)`. + * **Note:** Returned rotation is in degrees, not radians. + * **Deprecated:** Use `get_bone_override` instead. * `set_bone_override(bone, override)` * `bone`: string * `override`: `{ position = property, rotation = property, scale = property }` or `nil` @@ -7984,7 +7984,7 @@ child will follow movement and rotation of that bone. * Compatibility note: Clients prior to 5.9.0 only support absolute position and rotation. All values are treated as absolute and are set immediately (no interpolation). * `get_bone_override(bone)`: returns `override` in the above format - * **Note:** Unlike `get_bone_position`, the returned rotation is in radians, not degrees. + * **Note:** Unlike `get_bone_position`, the returned rotation is in radians, not degrees. * `get_bone_overrides()`: returns all bone overrides as table `{[bonename] = override, ...}` * `set_properties(object property table)` * `get_properties()`: returns a table of all object properties @@ -8081,8 +8081,8 @@ child will follow movement and rotation of that bone. * Fifth column: subject viewed from above * Sixth column: subject viewed from below * `get_luaentity()`: - * Returns the object's associated luaentity table, if there is one - * Otherwise returns `nil` (e.g. for players) + * Returns the object's associated luaentity table, if there is one + * Otherwise returns `nil` (e.g. for players) * `get_entity_name()`: * **Deprecated**: Will be removed in a future version, use `:get_luaentity().name` instead. diff --git a/doc/world_format.md b/doc/world_format.md index b5a2a3cfa..93920f391 100644 --- a/doc/world_format.md +++ b/doc/world_format.md @@ -394,7 +394,7 @@ Timestamp and node ID mappings were introduced in map format version 29. * `u8` `name_id_mapping_version` * Should be zero for map format version 29. - + * `u16` `num_name_id_mappings` * foreach `num_name_id_mappings`: * `u16` `id` diff --git a/irr/README.md b/irr/README.md index 96d3b0d97..640b82c6f 100644 --- a/irr/README.md +++ b/irr/README.md @@ -25,7 +25,7 @@ Aside from standard search options (`ZLIB_INCLUDE_DIR`, `ZLIB_LIBRARY`, ...) the * `USE_SDL2` (default: platform-dependent, usually `ON`) - Use SDL2 instead of older native device code However, IrrlichtMt cannot be built or installed separately. - + Platforms --------- diff --git a/lib/gmp/mini-gmp.c b/lib/gmp/mini-gmp.c index 69a72bfd4..ef9010be2 100644 --- a/lib/gmp/mini-gmp.c +++ b/lib/gmp/mini-gmp.c @@ -55,7 +55,7 @@ see https://www.gnu.org/licenses/. */ #include #endif - + /* Macros */ #define GMP_LIMB_BITS (sizeof(mp_limb_t) * CHAR_BIT) @@ -283,7 +283,7 @@ see https://www.gnu.org/licenses/. */ const int mp_bits_per_limb = GMP_LIMB_BITS; - + /* Memory allocation and other helper functions. */ static void gmp_die (const char *msg) @@ -384,7 +384,7 @@ gmp_free_limbs (mp_ptr old, mp_size_t size) gmp_free (old, size * sizeof (mp_limb_t)); } - + /* MPN interface */ void @@ -777,7 +777,7 @@ mpn_neg (mp_ptr rp, mp_srcptr up, mp_size_t n) return 1; } - + /* MPN division interface. */ /* The 3/2 inverse is defined as @@ -1169,7 +1169,7 @@ mpn_div_qr (mp_ptr qp, mp_ptr np, mp_size_t nn, mp_srcptr dp, mp_size_t dn) gmp_free_limbs (tp, dn); } - + /* MPN base conversion. */ static unsigned mpn_base_power_of_two_p (unsigned b) @@ -1425,7 +1425,7 @@ mpn_set_str (mp_ptr rp, const unsigned char *sp, size_t sn, int base) } } - + /* MPZ interface */ void mpz_init (mpz_t r) @@ -1480,7 +1480,7 @@ mpz_realloc (mpz_t r, mp_size_t size) #define MPZ_REALLOC(z,n) ((n) > (z)->_mp_alloc \ ? mpz_realloc(z,n) \ : (z)->_mp_d) - + /* MPZ assignment and basic conversions. */ void mpz_set_si (mpz_t r, signed long int x) @@ -1704,7 +1704,7 @@ mpz_roinit_n (mpz_t x, mp_srcptr xp, mp_size_t xs) return x; } - + /* Conversions and comparison to double. */ void mpz_set_d (mpz_t r, double x) @@ -1862,7 +1862,7 @@ mpz_cmp_d (const mpz_t x, double d) } } - + /* MPZ comparisons and the like. */ int mpz_sgn (const mpz_t u) @@ -1950,7 +1950,7 @@ mpz_swap (mpz_t u, mpz_t v) MPN_PTR_SWAP (u->_mp_d, u->_mp_size, v->_mp_d, v->_mp_size); } - + /* MPZ addition and subtraction */ @@ -2050,7 +2050,7 @@ mpz_sub (mpz_t r, const mpz_t a, const mpz_t b) r->_mp_size = a->_mp_size >= 0 ? rn : - rn; } - + /* MPZ multiplication */ void mpz_mul_si (mpz_t r, const mpz_t u, long int v) @@ -2186,7 +2186,7 @@ mpz_submul (mpz_t r, const mpz_t u, const mpz_t v) mpz_clear (t); } - + /* MPZ division */ enum mpz_div_round_mode { GMP_DIV_FLOOR, GMP_DIV_CEIL, GMP_DIV_TRUNC }; @@ -2661,7 +2661,7 @@ mpz_divisible_ui_p (const mpz_t n, unsigned long d) return mpz_div_qr_ui (NULL, NULL, n, d, GMP_DIV_TRUNC) == 0; } - + /* GCD */ static mp_limb_t mpn_gcd_11 (mp_limb_t u, mp_limb_t v) @@ -3054,7 +3054,7 @@ mpz_invert (mpz_t r, const mpz_t u, const mpz_t m) return invertible; } - + /* Higher level operations (sqrt, pow and root) */ void @@ -3334,7 +3334,7 @@ mpn_sqrtrem (mp_ptr sp, mp_ptr rp, mp_srcptr p, mp_size_t n) mpz_clear (r); return res; } - + /* Combinatorics */ void @@ -3378,7 +3378,7 @@ mpz_bin_uiui (mpz_t r, unsigned long n, unsigned long k) mpz_clear (t); } - + /* Primality testing */ /* Computes Kronecker (a/b) with odd b, a!=0 and GCD(a,b) = 1 */ @@ -3646,7 +3646,7 @@ mpz_probab_prime_p (const mpz_t n, int reps) return is_prime; } - + /* Logical operations and bit manipulation. */ /* Numbers are treated as if represented in two's complement (and @@ -4183,7 +4183,7 @@ mpz_scan0 (const mpz_t u, mp_bitcnt_t starting_bit) return mpn_common_scan (limb, i, up, un, ux); } - + /* MPZ base conversion. */ size_t @@ -4443,7 +4443,7 @@ mpz_out_str (FILE *stream, int base, const mpz_t x) return n; } - + static int gmp_detect_endian (void) { diff --git a/src/client/game.cpp b/src/client/game.cpp index 0906e9ca9..41c7972cb 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1446,7 +1446,7 @@ void Game::copyServerClientCache() { // It would be possible to let the client directly read the media files // from where the server knows they are. But aside from being more complicated - // it would also *not* fill the media cache and cause slower joining of + // it would also *not* fill the media cache and cause slower joining of // remote servers. // (Imagine that you launch a game once locally and then connect to a server.) diff --git a/src/client/mesh.h b/src/client/mesh.h index 0c3e8942e..106787af3 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -139,5 +139,5 @@ bool checkMeshNormals(scene::IMesh *mesh); Set the MinFilter, MagFilter and AnisotropicFilter properties of a texture layer according to the three relevant boolean values found in the Minetest settings. -*/ +*/ void setMaterialFilters(video::SMaterialLayer &tex, bool bilinear, bool trilinear, bool anisotropic); diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 1f49eed09..91992bc08 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -140,7 +140,7 @@ void ShadowRenderer::initialize() } createShaders(); - + m_texture_format = m_shadow_map_texture_32bit ? video::ECOLOR_FORMAT::ECF_R32F diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index 84da720db..9836f4b09 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -542,9 +542,9 @@ int LuaPcgRandom::l_set_state(lua_State *L) u64 state[2]; s_state_0 >> std::hex >> state[0]; s_state_1 >> std::hex >> state[1]; - + o->m_rnd.setState(state); - + return 0; } diff --git a/src/server/activeobjectmgr.cpp b/src/server/activeobjectmgr.cpp index c6f1010ea..f0216d9e3 100644 --- a/src/server/activeobjectmgr.cpp +++ b/src/server/activeobjectmgr.cpp @@ -91,7 +91,7 @@ bool ActiveObjectMgr::registerObject(std::unique_ptr obj) return false; } - auto obj_id = obj->getId(); + auto obj_id = obj->getId(); m_active_objects.put(obj_id, std::move(obj)); auto new_size = m_active_objects.size(); diff --git a/src/servermap.cpp b/src/servermap.cpp index eeabd74f9..0248497c1 100644 --- a/src/servermap.cpp +++ b/src/servermap.cpp @@ -52,7 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif /* - ServerMap + ServerMap */ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, From 48845de46ef400e532f2108dfb9ff9a3107bd66a Mon Sep 17 00:00:00 2001 From: cx384 Date: Sat, 17 Aug 2024 21:15:24 +0200 Subject: [PATCH 009/200] Fix trailing whitespace from #14945 --- doc/lua_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index b37959eed..4d461f335 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8940,7 +8940,7 @@ Player properties need to be saved manually. Entity definition ----------------- -Used by `minetest.register_entity`. +Used by `minetest.register_entity`. The entity definition table becomes a metatable of a newly created per-entity luaentity table, meaning its fields (e.g. `initial_properties`) will be shared between all instances of an entity. From b0107144261f6513bbde95323c9663a71ed8f3c9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 19 Aug 2024 09:17:52 +0200 Subject: [PATCH 010/200] [no sq] Move shaders & remove dead Irrlicht tests (#15006) * Move irrlicht shaders to correct place * Remove unused Irrlicht tests --- client/shaders/Irrlicht | 1 - .../shaders/Irrlicht}/OneTextureBlend.fsh | 0 .../shaders/Irrlicht}/Renderer2D.fsh | 0 .../shaders/Irrlicht}/Renderer2D.vsh | 0 .../shaders/Irrlicht}/Renderer2D_noTex.fsh | 0 .../shaders/Irrlicht}/Solid.fsh | 0 .../shaders/Irrlicht}/Solid.vsh | 0 .../Irrlicht}/TransparentAlphaChannel.fsh | 0 .../Irrlicht}/TransparentAlphaChannelRef.fsh | 0 .../Irrlicht}/TransparentVertexAlpha.fsh | 0 irr/.github/workflows/build.yml | 310 ------------------ irr/CMakeLists.txt | 9 - irr/examples/AutomatedTest/main.cpp | 154 --------- irr/examples/AutomatedTest/test_array.cpp | 138 -------- irr/examples/AutomatedTest/test_helper.h | 33 -- irr/examples/AutomatedTest/test_string.cpp | 205 ------------ irr/examples/CMakeLists.txt | 17 - irr/media/coolguy_opt.x | 2 - irr/media/cooltexture.png | Bin 4828 -> 0 bytes irr/scripts/ci-build-android.sh | 123 ------- irr/scripts/ci-build-mingw.sh | 70 ---- irr/scripts/ci-get-mingw.sh | 9 - irr/scripts/sha256sums.txt | 9 - irr/test/CMakeLists.txt | 32 -- irr/test/data/sample_16bpp_v3.bmp | Bin 654 -> 0 bytes irr/test/data/sample_16bpp_v7.bmp | Bin 738 -> 0 bytes irr/test/data/sample_24bpp.png | Bin 580 -> 0 bytes irr/test/data/sample_24bpp_down.tga | Bin 899 -> 0 bytes irr/test/data/sample_24bpp_rle_down.tga | Bin 844 -> 0 bytes irr/test/data/sample_24bpp_rle_up.tga | Bin 844 -> 0 bytes irr/test/data/sample_24bpp_up.tga | Bin 899 -> 0 bytes irr/test/data/sample_24bpp_v3.bmp | Bin 954 -> 0 bytes irr/test/data/sample_24bpp_v7.bmp | Bin 1038 -> 0 bytes irr/test/data/sample_4bpp_v3.bmp | Bin 298 -> 0 bytes irr/test/data/sample_8bpp.png | Bin 346 -> 0 bytes irr/test/data/sample_8bpp_down.tga | Bin 419 -> 0 bytes irr/test/data/sample_8bpp_rle_down.tga | Bin 444 -> 0 bytes irr/test/data/sample_8bpp_rle_up.tga | Bin 444 -> 0 bytes irr/test/data/sample_8bpp_up.tga | Bin 419 -> 0 bytes irr/test/data/sample_8bpp_v3.bmp | Bin 474 -> 0 bytes irr/test/data/sample_8bpp_v3_rle.bmp | Bin 524 -> 0 bytes irr/test/data/sample_8bpp_v7.bmp | Bin 558 -> 0 bytes irr/test/data/sample_8bpp_v7_rle.bmp | Bin 608 -> 0 bytes irr/test/image_loader_test.cpp | 162 --------- 44 files changed, 1274 deletions(-) delete mode 120000 client/shaders/Irrlicht rename {irr/media/Shaders => client/shaders/Irrlicht}/OneTextureBlend.fsh (100%) rename {irr/media/Shaders => client/shaders/Irrlicht}/Renderer2D.fsh (100%) rename {irr/media/Shaders => client/shaders/Irrlicht}/Renderer2D.vsh (100%) rename {irr/media/Shaders => client/shaders/Irrlicht}/Renderer2D_noTex.fsh (100%) rename {irr/media/Shaders => client/shaders/Irrlicht}/Solid.fsh (100%) rename {irr/media/Shaders => client/shaders/Irrlicht}/Solid.vsh (100%) rename {irr/media/Shaders => client/shaders/Irrlicht}/TransparentAlphaChannel.fsh (100%) rename {irr/media/Shaders => client/shaders/Irrlicht}/TransparentAlphaChannelRef.fsh (100%) rename {irr/media/Shaders => client/shaders/Irrlicht}/TransparentVertexAlpha.fsh (100%) delete mode 100644 irr/.github/workflows/build.yml delete mode 100644 irr/examples/AutomatedTest/main.cpp delete mode 100644 irr/examples/AutomatedTest/test_array.cpp delete mode 100644 irr/examples/AutomatedTest/test_helper.h delete mode 100644 irr/examples/AutomatedTest/test_string.cpp delete mode 100644 irr/examples/CMakeLists.txt delete mode 100755 irr/media/coolguy_opt.x delete mode 100755 irr/media/cooltexture.png delete mode 100755 irr/scripts/ci-build-android.sh delete mode 100755 irr/scripts/ci-build-mingw.sh delete mode 100755 irr/scripts/ci-get-mingw.sh delete mode 100644 irr/scripts/sha256sums.txt delete mode 100644 irr/test/CMakeLists.txt delete mode 100644 irr/test/data/sample_16bpp_v3.bmp delete mode 100644 irr/test/data/sample_16bpp_v7.bmp delete mode 100644 irr/test/data/sample_24bpp.png delete mode 100644 irr/test/data/sample_24bpp_down.tga delete mode 100644 irr/test/data/sample_24bpp_rle_down.tga delete mode 100644 irr/test/data/sample_24bpp_rle_up.tga delete mode 100644 irr/test/data/sample_24bpp_up.tga delete mode 100644 irr/test/data/sample_24bpp_v3.bmp delete mode 100644 irr/test/data/sample_24bpp_v7.bmp delete mode 100644 irr/test/data/sample_4bpp_v3.bmp delete mode 100644 irr/test/data/sample_8bpp.png delete mode 100644 irr/test/data/sample_8bpp_down.tga delete mode 100644 irr/test/data/sample_8bpp_rle_down.tga delete mode 100644 irr/test/data/sample_8bpp_rle_up.tga delete mode 100644 irr/test/data/sample_8bpp_up.tga delete mode 100644 irr/test/data/sample_8bpp_v3.bmp delete mode 100644 irr/test/data/sample_8bpp_v3_rle.bmp delete mode 100644 irr/test/data/sample_8bpp_v7.bmp delete mode 100644 irr/test/data/sample_8bpp_v7_rle.bmp delete mode 100644 irr/test/image_loader_test.cpp diff --git a/client/shaders/Irrlicht b/client/shaders/Irrlicht deleted file mode 120000 index 9349d3073..000000000 --- a/client/shaders/Irrlicht +++ /dev/null @@ -1 +0,0 @@ -../../irr/media/Shaders \ No newline at end of file diff --git a/irr/media/Shaders/OneTextureBlend.fsh b/client/shaders/Irrlicht/OneTextureBlend.fsh similarity index 100% rename from irr/media/Shaders/OneTextureBlend.fsh rename to client/shaders/Irrlicht/OneTextureBlend.fsh diff --git a/irr/media/Shaders/Renderer2D.fsh b/client/shaders/Irrlicht/Renderer2D.fsh similarity index 100% rename from irr/media/Shaders/Renderer2D.fsh rename to client/shaders/Irrlicht/Renderer2D.fsh diff --git a/irr/media/Shaders/Renderer2D.vsh b/client/shaders/Irrlicht/Renderer2D.vsh similarity index 100% rename from irr/media/Shaders/Renderer2D.vsh rename to client/shaders/Irrlicht/Renderer2D.vsh diff --git a/irr/media/Shaders/Renderer2D_noTex.fsh b/client/shaders/Irrlicht/Renderer2D_noTex.fsh similarity index 100% rename from irr/media/Shaders/Renderer2D_noTex.fsh rename to client/shaders/Irrlicht/Renderer2D_noTex.fsh diff --git a/irr/media/Shaders/Solid.fsh b/client/shaders/Irrlicht/Solid.fsh similarity index 100% rename from irr/media/Shaders/Solid.fsh rename to client/shaders/Irrlicht/Solid.fsh diff --git a/irr/media/Shaders/Solid.vsh b/client/shaders/Irrlicht/Solid.vsh similarity index 100% rename from irr/media/Shaders/Solid.vsh rename to client/shaders/Irrlicht/Solid.vsh diff --git a/irr/media/Shaders/TransparentAlphaChannel.fsh b/client/shaders/Irrlicht/TransparentAlphaChannel.fsh similarity index 100% rename from irr/media/Shaders/TransparentAlphaChannel.fsh rename to client/shaders/Irrlicht/TransparentAlphaChannel.fsh diff --git a/irr/media/Shaders/TransparentAlphaChannelRef.fsh b/client/shaders/Irrlicht/TransparentAlphaChannelRef.fsh similarity index 100% rename from irr/media/Shaders/TransparentAlphaChannelRef.fsh rename to client/shaders/Irrlicht/TransparentAlphaChannelRef.fsh diff --git a/irr/media/Shaders/TransparentVertexAlpha.fsh b/client/shaders/Irrlicht/TransparentVertexAlpha.fsh similarity index 100% rename from irr/media/Shaders/TransparentVertexAlpha.fsh rename to client/shaders/Irrlicht/TransparentVertexAlpha.fsh diff --git a/irr/.github/workflows/build.yml b/irr/.github/workflows/build.yml deleted file mode 100644 index f31521bd2..000000000 --- a/irr/.github/workflows/build.yml +++ /dev/null @@ -1,310 +0,0 @@ -name: build - -# build on c/cpp changes or workflow changes -on: - - push - - pull_request - -jobs: - - linux-gl: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - name: Install deps - run: | - sudo apt-get update - sudo apt-get install g++ cmake libxi-dev libgl1-mesa-dev libpng-dev libjpeg-dev zlib1g-dev -qyy - - - name: Build - run: | - cmake . -DUSE_SDL2=OFF - make VERBOSE=1 -j2 - - - name: Test - run: | - ctest --output-on-failure - - - name: Package - run: | - make DESTDIR=$PWD/_install install - tar -c -I "gzip -9" -f irrlicht-linux.tar.gz -C ./_install/usr/local . - - - uses: actions/upload-artifact@v4 - with: - name: irrlicht-linux - path: ./irrlicht-linux.tar.gz - - linux-gles: - # Xvfb test is broken on 20.04 for unknown reasons (not our bug) - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - name: Install deps - run: | - sudo apt-get update - sudo apt-get install g++ cmake libxi-dev libgles2-mesa-dev libpng-dev libjpeg-dev zlib1g-dev xvfb -qyy - - - name: Build - run: | - cmake . -DBUILD_EXAMPLES=1 -DUSE_SDL2=OFF -DENABLE_OPENGL=OFF -DENABLE_GLES2=ON - make -j2 - - - name: Test (headless) - run: | - cd bin/Linux - ./AutomatedTest null - - - name: Test (Xvfb) - run: | - cd bin/Linux - LIBGL_ALWAYS_SOFTWARE=true xvfb-run ./AutomatedTest ogles2 - - linux-sdl: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - name: Install deps - run: | - sudo apt-get update - sudo apt-get install g++ cmake libsdl2-dev libpng-dev libjpeg-dev zlib1g-dev -qyy - - - name: Build - run: | - cmake . -DBUILD_EXAMPLES=1 -DUSE_SDL2=ON -DCMAKE_BUILD_TYPE=Debug - make -j2 - - - name: Test (headless) - run: | - cd bin/Linux - ./AutomatedTest null - - linux-sdl-gl3: - # Xvfb test is broken on 20.04 for unknown reasons (not our bug) - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - name: Install deps - run: | - sudo apt-get update - sudo apt-get install g++ cmake libsdl2-dev libpng-dev libjpeg-dev zlib1g-dev xvfb -qyy - - - name: Build - run: | - cmake . -DBUILD_EXAMPLES=1 -DUSE_SDL2=ON -DENABLE_OPENGL=OFF -DENABLE_OPENGL3=ON - make -j2 - - - name: Test (headless) - run: | - cd bin/Linux - ./AutomatedTest null - - - name: Test (Xvfb) - run: | - cd bin/Linux - LIBGL_ALWAYS_SOFTWARE=true xvfb-run ./AutomatedTest opengl3 - - linux-sdl-gles2: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - name: Install deps - run: | - sudo apt-get update - sudo apt-get install g++ cmake libsdl2-dev libpng-dev libjpeg-dev zlib1g-dev xvfb -qyy - - - name: Build - run: | - cmake . -DBUILD_EXAMPLES=1 -DUSE_SDL2=ON -DENABLE_OPENGL=OFF -DENABLE_GLES2=ON - make -j2 - - - name: Test (headless) - run: | - cd bin/Linux - ./AutomatedTest null - - - name: Test (Xvfb) - run: | - cd bin/Linux - LIBGL_ALWAYS_SOFTWARE=true xvfb-run ./AutomatedTest ogles2 - - mingw: - name: "MinGW ${{matrix.config.variant}}${{matrix.config.extras}}" - runs-on: ubuntu-22.04 - strategy: - fail-fast: false - matrix: - config: - - {variant: win32, arch: i686} - - {variant: win64, arch: x86_64} - - {variant: win32, arch: i686, extras: "-sdl"} - - {variant: win64, arch: x86_64, extras: "-sdl"} - steps: - - uses: actions/checkout@v4 - - name: Install compiler - run: | - sudo apt-get update && sudo apt-get install cmake -qyy - ./scripts/ci-get-mingw.sh - - - name: Build - run: | - ./scripts/ci-build-mingw.sh package - env: - CC: ${{matrix.config.arch}}-w64-mingw32-clang - CXX: ${{matrix.config.arch}}-w64-mingw32-clang++ - extras: ${{matrix.config.extras}} - - - uses: actions/upload-artifact@v4 - with: - name: irrlicht-${{matrix.config.variant}}${{matrix.config.extras}} - path: ./irrlicht-${{matrix.config.variant}}${{matrix.config.extras}}.zip - - macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - name: Install deps - run: | - brew update --auto-update - brew install cmake libpng jpeg - env: - HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 - HOMEBREW_NO_INSTALL_CLEANUP: 1 - - - name: Build - run: | - cmake . -DCMAKE_FIND_FRAMEWORK=LAST -DBUILD_EXAMPLES=1 - make -j3 - - - name: Test (headless) - run: | - ./bin/OSX/AutomatedTest null - - macos-sdl: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - name: Install deps - run: | - brew update --auto-update - brew install cmake libpng jpeg sdl2 - env: - HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 - HOMEBREW_NO_INSTALL_CLEANUP: 1 - - - name: Build - run: | - cmake . -DCMAKE_FIND_FRAMEWORK=LAST -DBUILD_EXAMPLES=1 -DUSE_SDL2=1 - make -j3 - - msvc: - name: VS 2019 ${{ matrix.config.arch }} ${{ matrix.sdl.label }} - runs-on: windows-2019 - env: - VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50 - # 2023.10.19 - vcpkg_packages: zlib libpng libjpeg-turbo - strategy: - fail-fast: false - matrix: - config: - - - arch: x86 - generator: "-G'Visual Studio 16 2019' -A Win32" - vcpkg_triplet: x86-windows - - - arch: x64 - generator: "-G'Visual Studio 16 2019' -A x64" - vcpkg_triplet: x64-windows - sdl: - - - use: FALSE - label: '(no SDL)' - vcpkg_packages: opengl-registry - - - use: TRUE - label: '(with SDL)' - vcpkg_packages: sdl2 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Restore from cache and run vcpkg - uses: lukka/run-vcpkg@v7 - with: - vcpkgArguments: ${{env.vcpkg_packages}} ${{matrix.sdl.vcpkg_packages}} - vcpkgDirectory: '${{ github.workspace }}\vcpkg' - appendedCacheKey: ${{ matrix.config.vcpkg_triplet }} - vcpkgGitCommitId: ${{ env.VCPKG_VERSION }} - vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }} - - - name: CMake - run: | - cmake ${{matrix.config.generator}} ` - -DUSE_SDL2=${{matrix.sdl.use}} ` - -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" ` - -DCMAKE_BUILD_TYPE=Release . - - - name: Build - run: cmake --build . --config Release - - - name: Create artifact folder - run: | - mkdir artifact/ - mkdir artifact/lib/ - - - name: Move dlls into artifact folder - run: move bin\Win32-VisualStudio\Release\* artifact\lib\ - - - name: Move includes into artifact folder - run: move include artifact/ - - - name: Upload Artifact - uses: actions/upload-artifact@v4 - with: - name: msvc-${{ matrix.config.arch }}-${{matrix.sdl.use}} - path: artifact/ - - android: - name: Android ${{ matrix.arch }} - runs-on: ubuntu-20.04 - env: - ndk_version: "r25c" - ANDROID_NDK: ${{ github.workspace }}/android-ndk - strategy: - matrix: - arch: [armeabi-v7a, arm64-v8a, x86, x86_64] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install deps - run: | - sudo rm /var/lib/man-db/auto-update - sudo apt-get update - sudo apt-get install -qyy wget unzip zip gcc-multilib make cmake - - - name: Cache NDK - id: cache-ndk - uses: actions/cache@v4 - with: - key: android-ndk-${{ env.ndk_version }}-linux - path: ${{ env.ANDROID_NDK }} - - - name: Install NDK - run: | - wget --progress=bar:force "http://dl.google.com/android/repository/android-ndk-${ndk_version}-linux.zip" - unzip -q "android-ndk-${ndk_version}-linux.zip" - rm "android-ndk-${ndk_version}-linux.zip" - mv "android-ndk-${ndk_version}" "${ANDROID_NDK}" - if: ${{ steps.cache-ndk.outputs.cache-hit != 'true' }} - - - name: Build - run: ./scripts/ci-build-android.sh ${{ matrix.arch }} - - #- name: Upload Artifact - # uses: actions/upload-artifact@v4 - # with: - # name: irrlicht-android-${{ matrix.arch }} - # path: ${{ runner.temp }}/pkg/${{ matrix.arch }} diff --git a/irr/CMakeLists.txt b/irr/CMakeLists.txt index ccc00f271..dfd6b189a 100644 --- a/irr/CMakeLists.txt +++ b/irr/CMakeLists.txt @@ -11,14 +11,5 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type: Debug or Release" FORCE) endif() -# FIXME: tests need to be moved to MT if we want to keep them - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -#enable_testing() add_subdirectory(src) -#add_subdirectory(test) - -#option(BUILD_EXAMPLES "Build example applications" FALSE) -#if(BUILD_EXAMPLES) -# add_subdirectory(examples) -#endif() diff --git a/irr/examples/AutomatedTest/main.cpp b/irr/examples/AutomatedTest/main.cpp deleted file mode 100644 index c9e5bab68..000000000 --- a/irr/examples/AutomatedTest/main.cpp +++ /dev/null @@ -1,154 +0,0 @@ -#include -#include -#include "exampleHelper.h" - -using namespace irr; - -static IrrlichtDevice *device = nullptr; -static int test_fail = 0; - -void test_irr_array(); -void test_irr_string(); - -static video::E_DRIVER_TYPE chooseDriver(core::stringc arg_) -{ - if (arg_ == "null") - return video::EDT_NULL; - if (arg_ == "ogles1") - return video::EDT_OGLES1; - if (arg_ == "ogles2") - return video::EDT_OGLES2; - if (arg_ == "opengl") - return video::EDT_OPENGL; - if (arg_ == "opengl3") - return video::EDT_OPENGL3; - std::cerr << "Unknown driver type: " << arg_.c_str() << ". Trying OpenGL." << std::endl; - return video::EDT_OPENGL; -} - -static inline void check(bool ok, const char *msg) -{ - if (!ok) { - test_fail++; - device->getLogger()->log((core::stringc("FAILED TEST: ") + msg).c_str(), ELL_ERROR); - } -} - -void run_unit_tests() -{ - std::cout << "Running unit tests:" << std::endl; - try { - test_irr_array(); - test_irr_string(); - } catch (const std::exception &e) { - std::cerr << e.what() << std::endl; - test_fail++; - } - std::cout << std::endl; -} - -int main(int argc, char *argv[]) -{ - run_unit_tests(); - - SIrrlichtCreationParameters p; - p.DriverType = chooseDriver(argc > 1 ? argv[1] : ""); - p.WindowSize = core::dimension2du(640, 480); - p.Vsync = true; - p.LoggingLevel = ELL_DEBUG; - - device = createDeviceEx(p); - if (!device) - return 1; - - { - u32 total = 0; - device->getOSOperator()->getSystemMemory(&total, nullptr); - core::stringc message = core::stringc("Total RAM in MiB: ") + core::stringc(total >> 10); - device->getLogger()->log(message.c_str(), ELL_INFORMATION); - check(total > 130 * 1024, "RAM amount"); - } - - device->setWindowCaption(L"Hello World!"); - device->setResizable(true); - - video::IVideoDriver *driver = device->getVideoDriver(); - scene::ISceneManager *smgr = device->getSceneManager(); - gui::IGUIEnvironment *guienv = device->getGUIEnvironment(); - - guienv->addStaticText(L"sample text", core::rect(10, 10, 110, 22), false); - - gui::IGUIButton *button = guienv->addButton( - core::rect(10, 30, 110, 30 + 32), 0, -1, L"sample button", - L"sample tooltip"); - - gui::IGUIEditBox *editbox = guienv->addEditBox(L"", - core::rect(10, 70, 60, 70 + 16)); - - const io::path mediaPath = getExampleMediaPath(); - - auto mesh_file = device->getFileSystem()->createAndOpenFile(mediaPath + "coolguy_opt.x"); - check(mesh_file, "mesh file loading"); - scene::IAnimatedMesh *mesh = smgr->getMesh(mesh_file); - check(mesh, "mesh loading"); - if (mesh_file) - mesh_file->drop(); - if (mesh) { - video::ITexture *tex = driver->getTexture(mediaPath + "cooltexture.png"); - check(tex, "texture loading"); - scene::IAnimatedMeshSceneNode *node = smgr->addAnimatedMeshSceneNode(mesh); - if (node) { - node->forEachMaterial([tex](video::SMaterial &mat) { - mat.Lighting = false; - mat.setTexture(0, tex); - }); - node->setFrameLoop(0, 29); - node->setAnimationSpeed(30); - } - } - - smgr->addCameraSceneNode(0, core::vector3df(0, 4, 5), core::vector3df(0, 2, 0)); - - s32 n = 0; - SEvent event; - device->getTimer()->start(); - - while (device->run()) { - if (device->getTimer()->getTime() >= 1000) { - device->getTimer()->setTime(0); - ++n; - if (n == 1) { // Tooltip display - bzero(&event, sizeof(SEvent)); - event.EventType = irr::EET_MOUSE_INPUT_EVENT; - event.MouseInput.Event = irr::EMIE_MOUSE_MOVED; - event.MouseInput.X = button->getAbsolutePosition().getCenter().X; - event.MouseInput.Y = button->getAbsolutePosition().getCenter().Y; - device->postEventFromUser(event); - } else if (n == 2) // Text input focus - guienv->setFocus(editbox); - else if (n == 3) { // Keypress for Text input - bzero(&event, sizeof(SEvent)); - event.EventType = irr::EET_KEY_INPUT_EVENT; - event.KeyInput.Char = L'a'; - event.KeyInput.Key = KEY_KEY_A; - event.KeyInput.PressedDown = true; - device->postEventFromUser(event); - event.KeyInput.PressedDown = false; - device->postEventFromUser(event); - } else - device->closeDevice(); - } - - driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, - video::SColor(255, 100, 100, 150)); - smgr->drawAll(); - guienv->drawAll(); - driver->endScene(); - } - - check(core::stringw(L"a") == editbox->getText(), "EditBox text"); - - device->getLogger()->log("Done.", ELL_INFORMATION); - device->drop(); - return test_fail > 0 ? 1 : 0; -} diff --git a/irr/examples/AutomatedTest/test_array.cpp b/irr/examples/AutomatedTest/test_array.cpp deleted file mode 100644 index 42959e913..000000000 --- a/irr/examples/AutomatedTest/test_array.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include -#include "test_helper.h" - -using namespace irr; -using core::array; - -static void test_basics() -{ - array v; - v.push_back(1); // 1 - v.push_front(2); // 2, 1 - v.insert(4, 0); // 4, 2, 1 - v.insert(3, 1); // 4, 3, 2, 1 - v.insert(0, 4); // 4, 3, 2, 1, 0 - UASSERTEQ(v.size(), 5); - UASSERTEQ(v[0], 4); - UASSERTEQ(v[1], 3); - UASSERTEQ(v[2], 2); - UASSERTEQ(v[3], 1); - UASSERTEQ(v[4], 0); - array w = v; - UASSERTEQ(w.size(), 5); - UASSERT(w == v); - w.clear(); - UASSERTEQ(w.size(), 0); - UASSERTEQ(w.allocated_size(), 0); - UASSERT(w.empty()); - w = v; - UASSERTEQ(w.size(), 5); - w.set_used(3); - UASSERTEQ(w.size(), 3); - UASSERTEQ(w[0], 4); - UASSERTEQ(w[1], 3); - UASSERTEQ(w[2], 2); - UASSERTEQ(w.getLast(), 2); - w.set_used(20); - UASSERTEQ(w.size(), 20); - w = v; - w.sort(); - UASSERTEQ(w.size(), 5); - UASSERTEQ(w[0], 0); - UASSERTEQ(w[1], 1); - UASSERTEQ(w[2], 2); - UASSERTEQ(w[3], 3); - UASSERTEQ(w[4], 4); - w.erase(0); - UASSERTEQ(w.size(), 4); - UASSERTEQ(w[0], 1); - UASSERTEQ(w[1], 2); - UASSERTEQ(w[2], 3); - UASSERTEQ(w[3], 4); - w.erase(1, 2); - UASSERTEQ(w.size(), 2); - UASSERTEQ(w[0], 1); - UASSERTEQ(w[1], 4); - w.swap(v); - UASSERTEQ(w.size(), 5); - UASSERTEQ(v.size(), 2); -} - -static void test_linear_searches() -{ - // Populate the array with 0, 1, 2, ..., 100, 100, 99, 98, 97, ..., 0 - array arr; - for (int i = 0; i <= 100; i++) - arr.push_back(i); - for (int i = 100; i >= 0; i--) - arr.push_back(i); - s32 end = arr.size() - 1; - for (int i = 0; i <= 100; i++) { - s32 index = arr.linear_reverse_search(i); - UASSERTEQ(index, end - i); - } - for (int i = 0; i <= 100; i++) { - s32 index = arr.linear_search(i); - UASSERTEQ(index, i); - } -} - -static void test_binary_searches() -{ - const auto &values = {3, 5, 1, 2, 5, 10, 19, 9, 7, 1, 2, 5, 8, 15}; - array arr; - for (int value : values) { - arr.push_back(value); - } - // Test the const form first, it uses a linear search without sorting - const array &carr = arr; - UASSERTEQ(carr.binary_search(20), -1); - UASSERTEQ(carr.binary_search(0), -1); - UASSERTEQ(carr.binary_search(1), 2); - - // Sorted: 1, 1, 2, 2, 3, 5, 5, 5, 7, 8, 9, 10, 15, 19 - UASSERTEQ(arr.binary_search(20), -1); - UASSERTEQ(arr.binary_search(0), -1); - - for (int value : values) { - s32 i = arr.binary_search(value); - UASSERTNE(i, -1); - UASSERTEQ(arr[i], value); - } - - s32 first, last; - first = arr.binary_search_multi(1, last); - UASSERTEQ(first, 0); - UASSERTEQ(last, 1); - - first = arr.binary_search_multi(2, last); - UASSERTEQ(first, 2); - UASSERTEQ(last, 3); - - first = arr.binary_search_multi(3, last); - UASSERTEQ(first, 4); - UASSERTEQ(last, 4); - - first = arr.binary_search_multi(4, last); - UASSERTEQ(first, -1); - - first = arr.binary_search_multi(5, last); - UASSERTEQ(first, 5); - UASSERTEQ(last, 7); - - first = arr.binary_search_multi(7, last); - UASSERTEQ(first, 8); - UASSERTEQ(last, 8); - - first = arr.binary_search_multi(19, last); - UASSERTEQ(first, 13); - UASSERTEQ(last, 13); -} - -void test_irr_array() -{ - test_basics(); - test_linear_searches(); - test_binary_searches(); - std::cout << " test_irr_array PASSED" << std::endl; -} diff --git a/irr/examples/AutomatedTest/test_helper.h b/irr/examples/AutomatedTest/test_helper.h deleted file mode 100644 index 5229eff29..000000000 --- a/irr/examples/AutomatedTest/test_helper.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include - -class TestFailedException : public std::exception -{ -}; - -// Asserts the comparison specified by CMP is true, or fails the current unit test -#define UASSERTCMP(CMP, actual, expected) \ - do { \ - const auto &a = (actual); \ - const auto &e = (expected); \ - if (!CMP(a, e)) { \ - std::cout \ - << "Test assertion failed: " << #actual << " " << #CMP << " " \ - << #expected << std::endl \ - << " at " << __FILE__ << ":" << __LINE__ << std::endl \ - << " actual: " << a << std::endl \ - << " expected: " \ - << e << std::endl; \ - throw TestFailedException(); \ - } \ - } while (0) - -#define CMPEQ(a, e) (a == e) -#define CMPTRUE(a, e) (a) -#define CMPNE(a, e) (a != e) - -#define UASSERTEQ(actual, expected) UASSERTCMP(CMPEQ, actual, expected) -#define UASSERTNE(actual, nexpected) UASSERTCMP(CMPNE, actual, nexpected) -#define UASSERT(actual) UASSERTCMP(CMPTRUE, actual, true) diff --git a/irr/examples/AutomatedTest/test_string.cpp b/irr/examples/AutomatedTest/test_string.cpp deleted file mode 100644 index 4d1291f18..000000000 --- a/irr/examples/AutomatedTest/test_string.cpp +++ /dev/null @@ -1,205 +0,0 @@ -#include -#include -#include -#include -#include "test_helper.h" - -using namespace irr; -using namespace irr::core; - -#define CMPSTR(a, b) (!strcmp(a, b)) -#define UASSERTSTR(actual, expected) UASSERTCMP(CMPSTR, actual.c_str(), expected) - -static void test_basics() -{ - // ctor - stringc s; - UASSERTEQ(s.c_str()[0], '\0'); - s = stringc(0.1234567); - UASSERTSTR(s, "0.123457"); - s = stringc(0x1p+53); - UASSERTSTR(s, "9007199254740992.000000"); - s = stringc(static_cast(-102400)); - UASSERTSTR(s, "-102400"); - s = stringc(static_cast(102400)); - UASSERTSTR(s, "102400"); - s = stringc(static_cast(-1024000)); - UASSERTSTR(s, "-1024000"); - s = stringc(static_cast(1024000)); - UASSERTSTR(s, "1024000"); - s = stringc("YESno", 3); - UASSERTSTR(s, "YES"); - s = stringc(L"test", 4); - UASSERTSTR(s, "test"); - s = stringc("Hello World!"); - UASSERTSTR(s, "Hello World!"); - // operator= - s = stringw(L"abcdef"); - UASSERTSTR(s, "abcdef"); - s = L"abcdef"; - UASSERTSTR(s, "abcdef"); - s = static_cast(nullptr); - UASSERTSTR(s, ""); - // operator+ - s = s + stringc("foo"); - UASSERTSTR(s, "foo"); - s = s + L"bar"; - UASSERTSTR(s, "foobar"); - // the rest - s = "f"; - UASSERTEQ(s[0], 'f'); - const auto &sref = s; - UASSERTEQ(sref[0], 'f'); - UASSERT(sref == "f"); - UASSERT(sref == stringc("f")); - s = "a"; - UASSERT(sref < stringc("aa")); - UASSERT(sref < stringc("b")); - UASSERT(stringc("Z") < sref); - UASSERT(!(sref < stringc("a"))); - UASSERT(sref.lower_ignore_case("AA")); - UASSERT(sref.lower_ignore_case("B")); - UASSERT(!sref.lower_ignore_case("A")); - s = "dog"; - UASSERT(sref != "cat"); - UASSERT(sref != stringc("cat")); -} - -static void test_methods() -{ - stringc s; - const auto &sref = s; - s = "irrlicht"; - UASSERTEQ(sref.size(), 8); - UASSERT(!sref.empty()); - s.clear(); - UASSERTEQ(sref.size(), 0); - UASSERT(sref.empty()); - UASSERT(sref[0] == 0); - s = "\tAz#`"; - s.make_lower(); - UASSERTSTR(s, "\taz#`"); - s.make_upper(); - UASSERTSTR(s, "\tAZ#`"); - UASSERT(sref.equals_ignore_case("\taz#`")); - UASSERT(sref.equals_substring_ignore_case("Z#`", 2)); - s = "irrlicht"; - UASSERT(sref.equalsn(stringc("irr"), 3)); - UASSERT(sref.equalsn("irr", 3)); - s = "fo"; - s.append('o'); - UASSERTSTR(s, "foo"); - s.append("bar", 1); - UASSERTSTR(s, "foob"); - s.append("ar", 999999); - UASSERTSTR(s, "foobar"); - s = "nyan"; - s.append(stringc("cat")); - UASSERTSTR(s, "nyancat"); - s.append(stringc("sam"), 1); - UASSERTSTR(s, "nyancats"); - s = "fbar"; - s.insert(1, "ooXX", 2); - UASSERTSTR(s, "foobar"); - UASSERTEQ(sref.findFirst('o'), 1); - UASSERTEQ(sref.findFirst('X'), -1); - UASSERTEQ(sref.findFirstChar("abff", 2), 3); - UASSERTEQ(sref.findFirstCharNotInList("fobb", 2), 3); - UASSERTEQ(sref.findLast('o'), 2); - UASSERTEQ(sref.findLast('X'), -1); - UASSERTEQ(sref.findLastChar("abrr", 2), 4); - UASSERTEQ(sref.findLastCharNotInList("rabb", 2), 3); - UASSERTEQ(sref.findNext('o', 2), 2); - UASSERTEQ(sref.findLast('o', 1), 1); - s = "ob-oob"; - UASSERTEQ(sref.find("ob", 1), 4); - UASSERTEQ(sref.find("ob"), 0); - UASSERTEQ(sref.find("?"), -1); - s = "HOMEOWNER"; - stringc s2 = sref.subString(2, 4); - UASSERTSTR(s2, "MEOW"); - s2 = sref.subString(2, 4, true); - UASSERTSTR(s2, "meow"); - s = "land"; - s.replace('l', 's'); - UASSERTSTR(s, "sand"); - s = ">dog<"; - s.replace("dog", "cat"); - UASSERTSTR(s, ">cat<"); - s.replace("cat", "horse"); - UASSERTSTR(s, ">horse<"); - s.replace("horse", "gnu"); - UASSERTSTR(s, ">gnu<"); - s = " h e l p "; - s.remove(' '); - UASSERTSTR(s, "help"); - s.remove("el"); - UASSERTSTR(s, "hp"); - s = "irrlicht"; - s.removeChars("it"); - UASSERTSTR(s, "rrlch"); - s = "\r\nfoo bar "; - s.trim(); - UASSERTSTR(s, "foo bar"); - s = "foxo"; - s.erase(2); - UASSERTSTR(s, "foo"); - s = "a"; - s.append('\0'); - s.append('b'); - UASSERTEQ(s.size(), 3); - s.validate(); - UASSERTEQ(s.size(), 1); - UASSERTEQ(s.lastChar(), 'a'); - std::vector res; - s = "a,,b,c"; - s.split(res, ",aa", 1, true, false); - UASSERTEQ(res.size(), 3); - UASSERTSTR(res[0], "a"); - UASSERTSTR(res[2], "c"); - res.clear(); - s.split(res, ",", 1, false, true); - UASSERTEQ(res.size(), 7); - UASSERTSTR(res[0], "a"); - UASSERTSTR(res[2], ""); - for (int i = 0; i < 3; i++) - UASSERTSTR(res[2 * i + 1], ","); -} - -static void test_conv() -{ - // locale-independent - - stringw out; - utf8ToWString(out, "†††"); - UASSERTEQ(out.size(), 3); - for (int i = 0; i < 3; i++) - UASSERTEQ(static_cast(out[i]), 0x2020); - - stringc out2; - wStringToUTF8(out2, L"†††"); - UASSERTEQ(out2.size(), 9); - for (int i = 0; i < 3; i++) { - UASSERTEQ(static_cast(out2[3 * i]), 0xe2); - UASSERTEQ(static_cast(out2[3 * i + 1]), 0x80); - UASSERTEQ(static_cast(out2[3 * i + 2]), 0xa0); - } - - // locale-dependent - if (!setlocale(LC_CTYPE, "C.UTF-8")) - setlocale(LC_CTYPE, "UTF-8"); // macOS - - stringw out3; - multibyteToWString(out3, "†††"); - UASSERTEQ(out3.size(), 3); - for (int i = 0; i < 3; i++) - UASSERTEQ(static_cast(out3[i]), 0x2020); -} - -void test_irr_string() -{ - test_basics(); - test_methods(); - test_conv(); - std::cout << " test_irr_string PASSED" << std::endl; -} diff --git a/irr/examples/CMakeLists.txt b/irr/examples/CMakeLists.txt deleted file mode 100644 index 03553048e..000000000 --- a/irr/examples/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -set(IRREXAMPLES - # removed -) -if(UNIX) - list(APPEND IRREXAMPLES AutomatedTest) -endif() - -foreach(exname IN ITEMS ${IRREXAMPLES}) - file(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/${exname}/*.cpp") - add_executable(${exname} ${sources}) - - target_include_directories(${exname} PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/${exname} - ) - target_link_libraries(${exname} IrrlichtMt) -endforeach() diff --git a/irr/media/coolguy_opt.x b/irr/media/coolguy_opt.x deleted file mode 100755 index e806d8315..000000000 --- a/irr/media/coolguy_opt.x +++ /dev/null @@ -1,2 +0,0 @@ -xof 0303txt 0032 -AnimationSet{Animation{{Armature}AnimationKey{0;2;0;4;1,0,0,0;;,29;4;1,0,0,0;;;}AnimationKey{2;2;0;3;0,0,0;;,29;3;0,0,0;;;}}Animation{{Armature_knee_r}AnimationKey{0;16;0;4;0.864183,0.503177,0,0;;,1;4;0.829812,0.558043,0,0;;,3;4;0.708698,0.705512,0,0;;,5;4;0.589108,0.808054,0,0;;,7;4;0.593659,0.804717,0,0;;,9;4;0.748627,0.662991,0,0;;,11;4;0.910305,0.413938,0,0;;,13;4;0.975925,0.218107,0,0;;,15;4;0.981302,0.192476,0,0;;,17;4;0.975476,0.220108,0,0;;,19;4;0.963662,0.267124,0,0;;,21;4;0.945893,0.324478,0,0;;,23;4;0.923816,0.382838,0,0;;,25;4;0.901205,0.433394,0,0;;,27;4;0.883429,0.468566,0,0;;,29;4;0.876305,0.481757,0,0;;;}AnimationKey{2;2;0;3;0,0,1.10139;;,29;3;0,0,1.10139;;;}}Animation{{Armature_elbow_r}AnimationKey{0;16;0;4;0.756295,0.004619,-0.619265,0.210967;;,1;4;0.771977,0.005599,-0.60257,0.202311;;,3;4;0.825501,0.009164,-0.538259,0.169533;;,5;4;0.891859,0.014253,-0.436142,0.119019;;,7;4;0.949154,0.019821,-0.308768,0.058108;;,9;4;0.983251,0.024703,-0.18057,-0.001258;;,11;4;0.995416,0.028143,-0.07812,-0.047458;;,13;4;0.996672,0.02991,-0.020368,-0.073041;;,15;4;0.996672,0.02991,-0.020368,-0.073041;;,17;4;0.995416,0.028143,-0.07812,-0.047458;;,19;4;0.983251,0.024703,-0.18057,-0.001258;;,21;4;0.949154,0.019821,-0.308768,0.058108;;,23;4;0.891859,0.014253,-0.436142,0.119019;;,25;4;0.825501,0.009164,-0.538259,0.169533;;,27;4;0.771977,0.005599,-0.60257,0.202311;;,29;4;0.750682,0.004275,-0.625038,0.213976;;;}AnimationKey{2;2;0;3;0,0,0.754892;;,29;3;0,0,0.754892;;;}}Animation{{Armature_arm_r}AnimationKey{0;16;0;4;0.28219,0.629905,0.723388,-0.017285;;,1;4;0.277641,0.632543,0.722699,-0.022614;;,3;4;0.261375,0.641615,0.719924,-0.041507;;,5;4;0.238321,0.653533,0.715186,-0.067874;;,7;4;0.212026,0.665838,0.708676,-0.097381;;,9;4;0.186345,0.676585,0.701229,-0.125643;;,11;4;0.165298,0.684491,0.694351,-0.14841;;,13;4;0.152894,0.688778,0.68998,-0.161665;;,15;4;0.152894,0.688779,0.68998,-0.161665;;,17;4;0.165298,0.684491,0.694351,-0.14841;;,19;4;0.186345,0.676585,0.701229,-0.125643;;,21;4;0.212026,0.665838,0.708676,-0.097381;;,23;4;0.238321,0.653533,0.715186,-0.067874;;,25;4;0.261375,0.641615,0.719924,-0.041507;;,27;4;0.277641,0.632543,0.722699,-0.022614;;,29;4;0.283802,0.628959,0.723623,-0.015394;;;}AnimationKey{2;2;0;3;-0.545315,0,1;;,29;3;-0.545315,0,1;;;}}Animation{{Armature_knee_l}AnimationKey{0;16;0;4;0.981896,0.189423,0,0;;,1;4;0.9814,0.191974,0,0;;,3;4;0.979127,0.203251,0,0;;,5;4;0.974526,0.224276,0,0;;,7;4;0.96645,0.256853,0,0;;,9;4;0.953088,0.302692,0,0;;,11;4;0.931731,0.36315,0,0;;,13;4;0.898645,0.438676,0,0;;,15;4;0.848226,0.529634,0,0;;,17;4;0.773692,0.633562,0,0;;,19;4;0.689831,0.72397,0,0;;,21;4;0.629304,0.777159,0,0;;,23;4;0.648685,0.761057,0,0;;,25;4;0.812268,0.583284,0,0;;,27;4;0.948066,0.318074,0,0;;,29;4;0.982049,0.188624,0,0;;;}AnimationKey{2;2;0;3;0,0,1.10139;;,29;3;0,0,1.10139;;;}}Animation{{Armature_Bone_007}AnimationKey{0;16;0;4;0.993671,-0.112331,0,0;;,1;4;0.994784,-0.102002,0,0;;,3;4;0.997507,-0.070564,0,0;;,5;4;0.999237,-0.039056,0,0;;,7;4;0.999694,-0.024737,0,0;;,9;4;0.999079,-0.042907,0,0;;,11;4;0.99677,-0.080308,0,0;;,13;4;0.993798,-0.111199,0,0;;,15;4;0.993599,-0.112965,0,0;;,17;4;0.995813,-0.091409,0,0;;,19;4;0.998181,-0.060285,0,0;;,21;4;0.999479,-0.032286,0,0;;,23;4;0.999797,-0.020142,0,0;;,25;4;0.998983,-0.045097,0,0;;,27;4;0.995813,-0.091409,0,0;;,29;4;0.993221,-0.116243,0,0;;;}AnimationKey{2;2;0;3;0,0,1.221802;;,29;3;0,0,1.221802;;;}}Animation{{Armature_elbow_l}AnimationKey{0;16;0;4;0.995195,-0.034868,-0.015799,-0.090119;;,1;4;0.993465,-0.046368,-0.030155,-0.099838;;,3;4;0.983557,-0.0879,-0.082099,-0.134715;;,5;4;0.959324,-0.146904,-0.156177,-0.183648;;,7;4;0.917546,-0.212233,-0.238611,-0.236921;;,9;4;0.864109,-0.271657,-0.314022,-0.284443;;,11;4;0.813172,-0.315829,-0.370387,-0.319087;;,13;4;0.781004,-0.339668,-0.400938,-0.337501;;,15;4;0.781004,-0.339668,-0.400938,-0.337501;;,17;4;0.813172,-0.315829,-0.370387,-0.319087;;,19;4;0.864109,-0.271657,-0.314022,-0.284443;;,21;4;0.917546,-0.212233,-0.238611,-0.236921;;,23;4;0.959324,-0.146904,-0.156177,-0.183648;;,25;4;0.983557,-0.0879,-0.082099,-0.134715;;,27;4;0.993465,-0.046368,-0.030155,-0.099838;;,29;4;0.995701,-0.030812,-0.010739,-0.086685;;;}AnimationKey{2;2;0;3;0,0,0.754892;;,29;3;0,0,0.754892;;;}}Animation{{Armature_body}AnimationKey{0;16;0;4;-0,0,0.601298,0.799025;;,1;4;-0,0,0.608144,0.793827;;,3;4;-0,0,0.627465,0.778645;;,5;4;-0,0,0.643183,0.765712;;,7;4;-0,0,0.643755,0.765231;;,9;4;-0,0,0.631076,0.775721;;,11;4;-0,0,0.613775,0.789481;;,13;4;-0,0,0.6007,0.799474;;,15;4;-0,0,0.601488,0.798882;;,17;4;-0,0,0.619499,0.784997;;,19;4;-0,0,0.643196,0.765702;;,21;4;-0,0,0.660441,0.750878;;,23;4;-0,0,0.659666,0.751559;;,25;4;-0,0,0.638264,0.769817;;,27;4;-0,0,0.611752,0.791049;;,29;4;-0,0,0.598631,0.801025;;;}AnimationKey{2;2;0;3;0,2.580534,0;;,29;3;0,2.571201,0;;;}}Animation{{Armature_leg_l}AnimationKey{0;16;0;4;0.390287,0.920693,0,0;;,1;4;0.362565,0.931959,0,0;;,3;4;0.266163,0.963928,0,0;;,5;4;0.138294,0.990391,0,0;;,7;4;0.012725,0.999919,0,0;;,9;4;-0.090194,0.995924,0,0;;,11;4;-0.162502,0.986708,0,0;;,13;4;-0.201466,0.979496,0,0;;,15;4;-0.185641,0.982618,0,0;;,17;4;-0.013697,0.999906,0,0;;,19;4;0.24238,0.970181,0,0;;,21;4;0.417271,0.908782,0,0;;,23;4;0.439308,0.898336,0,0;;,25;4;0.424255,0.905543,0,0;;,27;4;0.407664,0.913132,0,0;;,29;4;0.400263,0.9164,0,0;;;}AnimationKey{2;2;0;3;0.246294,0,-0.171352;;,29;3;0.246294,0,-0.171351;;;}}Animation{{Armature_leg_r}AnimationKey{0;16;0;4;0.174933,-0.98458,0,0;;,1;4;0.082829,-0.996564,0,0;;,3;4;-0.21147,-0.977384,0,0;;,5;4;-0.442802,-0.89662,0,0;;,7;4;-0.47604,-0.879424,0,0;;,9;4;-0.47279,-0.881175,0,0;;,11;4;-0.459567,-0.888143,0,0;;,13;4;-0.427425,-0.904051,0,0;;,15;4;-0.361724,-0.932285,0,0;;,17;4;-0.251362,-0.967893,0,0;;,19;4;-0.114531,-0.99342,0,0;;,21;4;0.021053,-0.999778,0,0;;,23;4;0.12473,-0.992191,0,0;;,25;4;0.181473,-0.983396,0,0;;,27;4;0.204037,-0.978963,0,0;;,29;4;0.208187,-0.978089,0,0;;;}AnimationKey{2;2;0;3;-0.246294,0,-0.171352;;,29;3;-0.246294,0,-0.171351;;;}}Animation{{Armature_arm_l}AnimationKey{0;16;0;4;0.200754,-0.659656,-0.716264,-0.107316;;,1;4;0.192268,-0.660735,-0.716526,-0.114246;;,3;4;0.161871,-0.663925,-0.716753,-0.138802;;,5;4;0.118745,-0.666682,-0.715211,-0.17294;;,7;4;0.069733,-0.667364,-0.710872,-0.210767;;,9;4;0.022313,-0.665594,-0.704111,-0.246404;;,11;4;-0.016046,-0.662426,-0.696821,-0.274543;;,13;4;-0.038374,-0.659874,-0.691824,-0.290643;;,15;4;-0.038373,-0.659874,-0.691824,-0.290643;;,17;4;-0.016044,-0.662427,-0.696822,-0.274543;;,19;4;0.022312,-0.665594,-0.70411,-0.246404;;,21;4;0.069733,-0.667365,-0.710872,-0.210767;;,23;4;0.118745,-0.666682,-0.715211,-0.17294;;,25;4;0.161871,-0.663925,-0.716753,-0.138802;;,27;4;0.192268,-0.660735,-0.716526,-0.114246;;,29;4;0.203757,-0.659255,-0.716151,-0.104856;;;}AnimationKey{2;2;0;3;0.545315,0,1;;,29;3;0.545315,0,1;;;}}}Frame Root{FrameTransformMatrix{1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1;;}Frame Armature{FrameTransformMatrix{1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1;;}Frame Armature_body{FrameTransformMatrix{-1,0,0,0,0,0,1,0,0,1,0,0,0,2.571201,0,1;;}Frame Armature_arm_r{FrameTransformMatrix{-0.047733,0.997488,-0.05233,0,0.901521,0.020464,-0.432251,0,-0.430095,-0.067809,-0.900233,0,-0.545315,0,1,1;;}Frame Armature_elbow_r{FrameTransformMatrix{0.987983,0.151721,-0.029519,0,-0.153228,0.986478,-0.058162,0,0.020295,0.061987,0.997871,0,0,0,0.754892,1;;}}}Frame Armature_arm_l{FrameTransformMatrix{-0.047732,0.994072,-0.097683,0,0.901521,0.084983,0.424309,0,0.430095,-0.067809,-0.900233,0,0.545315,0,1,1;;}Frame Armature_elbow_l{FrameTransformMatrix{0.984741,0.173286,-0.016044,0,-0.171963,0.983073,0.063221,0,0.026727,-0.059497,0.99787,0,0,0,0.754892,1;;}}}Frame Armature_leg_l{FrameTransformMatrix{1,0,0,0,0,-0.998426,-0.056453,0,0,0.056453,-0.998405,0,0.246294,0,-0.171351,1;;}Frame Armature_knee_l{FrameTransformMatrix{1,0,0,0,0,0.993861,-0.110639,0,0,0.110639,0.993861,0,0,0,1.10139,1;;}}}Frame Armature_leg_r{FrameTransformMatrix{1,0,0,0,0,-0.998426,-0.056453,0,0,0.056453,-0.998405,0,-0.246294,0,-0.171351,1;;}Frame Armature_knee_r{FrameTransformMatrix{1,0,0,0,0,0.993861,-0.110639,0,0,0.110639,0.993861,0,0,0,1.10139,1;;}}}Frame Armature_Bone_007{FrameTransformMatrix{1,0,0,0,0,1,0,0,0,0,1,0,0,0,1.221802,1;;}}}Frame cool_dude{FrameTransformMatrix{-1,0,0,0,0,1,0,0,0,0,-1,0,0,0,0,1;;}Mesh{272;0;2.440814;0.219926;,0;3.688199;0.219926;,0.466212;3.688199;0.219926;,0.466212;2.440814;0.219926;,0.466212;2.440814;0.219926;,0.466212;3.688199;0.219926;,0.466212;3.688199;-0.219926;,0.466212;2.440814;-0.219926;,0;2.440814;0.219926;,0.466212;2.440814;0.219926;,0.466212;2.440814;-0.219926;,0;2.440814;-0.219926;,0.055633;1.27575;-0.190081;,0.055633;2.35741;-0.190081;,0.055633;2.35741;0.190081;,0.055633;1.27575;0.190081;,0.055633;1.27575;0.190081;,0.055633;2.35741;0.190081;,0.43017;2.35741;0.190081;,0.43017;1.27575;0.190081;,0.43017;1.27575;0.190081;,0.43017;2.35741;0.190081;,0.43017;2.35741;-0.190081;,0.43017;1.27575;-0.190081;,0.43017;1.27575;-0.190081;,0.43017;2.35741;-0.190081;,0.055633;2.35741;-0.190081;,0.055633;1.27575;-0.190081;,0.055633;1.27575;0.190081;,0.43017;1.27575;0.190081;,0.43017;1.27575;-0.190081;,0.055633;1.27575;-0.190081;,0.43017;2.35741;0.190081;,0.055633;2.35741;0.190081;,0.055633;2.35741;-0.190081;,0.43017;2.35741;-0.190081;,0.466212;3.688199;0.219926;,0;3.688199;0.219926;,0;3.688199;-0.219926;,0.466212;3.688199;-0.219926;,0.466212;2.440814;-0.219926;,0.466212;3.688199;-0.219926;,0;3.688199;-0.219926;,0;2.440814;-0.219926;,0.769341;2.834949;-0.041122;,0.440953;3.555781;-0.041122;,0.440953;3.555781;0.207294;,0.769341;2.834949;0.207294;,0.769341;2.834949;0.207294;,0.440953;3.555781;0.207294;,0.616273;3.635651;0.207294;,0.944661;2.914819;0.207294;,0.944661;2.914819;0.207294;,0.616273;3.635651;0.207294;,0.616273;3.635651;-0.041122;,0.944661;2.914819;-0.041122;,0.944661;2.914819;-0.041122;,0.616273;3.635651;-0.041122;,0.440953;3.555781;-0.041122;,0.769341;2.834949;-0.041122;,0.769341;2.834949;0.207294;,0.944661;2.914819;0.207294;,0.944661;2.914819;-0.041122;,0.769341;2.834949;-0.041122;,0.616273;3.635651;0.207294;,0.440953;3.555781;0.207294;,0.440953;3.555781;-0.041122;,0.616273;3.635651;-0.041122;,1.104504;2.080977;-0.086788;,0.776116;2.801809;-0.086788;,0.776116;2.801809;0.161627;,1.104504;2.080977;0.161627;,1.104504;2.080977;0.161627;,0.776116;2.801809;0.161627;,0.951436;2.881679;0.161627;,1.279824;2.160847;0.161627;,1.279824;2.160847;0.161627;,0.951436;2.881679;0.161627;,0.951436;2.881679;-0.086788;,1.279824;2.160847;-0.086788;,1.279824;2.160847;-0.086788;,0.951436;2.881679;-0.086788;,0.776116;2.801809;-0.086788;,1.104504;2.080977;-0.086788;,1.104504;2.080977;0.161627;,1.279824;2.160847;0.161627;,1.279824;2.160847;-0.086788;,1.104504;2.080977;-0.086788;,0.951436;2.881679;0.161627;,0.776116;2.801809;0.161627;,0.776116;2.801809;-0.086788;,0.951436;2.881679;-0.086788;,0.055633;0.093601;-0.190081;,0.055633;1.205294;-0.190081;,0.055633;1.205294;0.190081;,0.055633;0.093601;0.190081;,0.055633;0.093601;0.190081;,0.055633;1.205294;0.190081;,0.43017;1.205294;0.190081;,0.43017;0.093601;0.190081;,0.43017;0.093601;0.190081;,0.43017;1.205294;0.190081;,0.43017;1.205294;-0.190081;,0.43017;0.093601;-0.190081;,0.43017;0.093601;-0.190081;,0.43017;1.205294;-0.190081;,0.055633;1.205294;-0.190081;,0.055633;0.093601;-0.190081;,0.055633;0.093601;0.190081;,0.43017;0.093601;0.190081;,0.43017;0.093601;-0.190081;,0.055633;0.093601;-0.190081;,0.43017;1.205294;0.190081;,0.055633;1.205294;0.190081;,0.055633;1.205294;-0.190081;,0.43017;1.205294;-0.190081;,0;3.790919;0.428464;,0;4.579204;0.428464;,0.43344;4.560537;0.409797;,0.43344;3.809586;0.409797;,0.43344;3.809586;0.409797;,0.43344;4.560537;0.409797;,0.43344;4.560537;-0.284975;,0.43344;3.809586;-0.284975;,0;3.790919;0.428464;,0.43344;3.809586;0.409797;,0.43344;3.809586;-0.284975;,0;3.790919;-0.303642;,0.43344;4.560537;0.409797;,0;4.579204;0.428464;,0;4.579204;-0.303642;,0.43344;4.560537;-0.284975;,0.43344;3.809586;-0.284975;,0.43344;4.560537;-0.284975;,0;4.579204;-0.303642;,0;3.790919;-0.303642;,0;2.440814;0.219926;,-0.466212;2.440814;0.219926;,-0.466212;3.688199;0.219926;,0;3.688199;0.219926;,-0.466212;2.440814;0.219926;,-0.466212;2.440814;-0.219926;,-0.466212;3.688199;-0.219926;,-0.466212;3.688199;0.219926;,0;2.440814;0.219926;,0;2.440814;-0.219926;,-0.466212;2.440814;-0.219926;,-0.466212;2.440814;0.219926;,-0.055633;1.27575;-0.190081;,-0.055633;1.27575;0.190081;,-0.055633;2.35741;0.190081;,-0.055633;2.35741;-0.190081;,-0.055633;1.27575;0.190081;,-0.43017;1.27575;0.190081;,-0.43017;2.35741;0.190081;,-0.055633;2.35741;0.190081;,-0.43017;1.27575;0.190081;,-0.43017;1.27575;-0.190081;,-0.43017;2.35741;-0.190081;,-0.43017;2.35741;0.190081;,-0.43017;1.27575;-0.190081;,-0.055633;1.27575;-0.190081;,-0.055633;2.35741;-0.190081;,-0.43017;2.35741;-0.190081;,-0.055633;1.27575;0.190081;,-0.055633;1.27575;-0.190081;,-0.43017;1.27575;-0.190081;,-0.43017;1.27575;0.190081;,-0.43017;2.35741;0.190081;,-0.43017;2.35741;-0.190081;,-0.055633;2.35741;-0.190081;,-0.055633;2.35741;0.190081;,-0.466212;3.688199;0.219926;,-0.466212;3.688199;-0.219926;,0;3.688199;-0.219926;,0;3.688199;0.219926;,-0.466212;2.440814;-0.219926;,0;2.440814;-0.219926;,0;3.688199;-0.219926;,-0.466212;3.688199;-0.219926;,-0.769341;2.834949;-0.041122;,-0.769341;2.834949;0.207294;,-0.440953;3.555781;0.207294;,-0.440953;3.555781;-0.041122;,-0.769341;2.834949;0.207294;,-0.944661;2.914819;0.207294;,-0.616273;3.635651;0.207294;,-0.440953;3.555781;0.207294;,-0.944661;2.914819;0.207294;,-0.944661;2.914819;-0.041122;,-0.616273;3.635651;-0.041122;,-0.616273;3.635651;0.207294;,-0.944661;2.914819;-0.041122;,-0.769341;2.834949;-0.041122;,-0.440953;3.555781;-0.041122;,-0.616273;3.635651;-0.041122;,-0.769341;2.834949;0.207294;,-0.769341;2.834949;-0.041122;,-0.944661;2.914819;-0.041122;,-0.944661;2.914819;0.207294;,-0.616273;3.635651;0.207294;,-0.616273;3.635651;-0.041122;,-0.440953;3.555781;-0.041122;,-0.440953;3.555781;0.207294;,-1.104504;2.080977;-0.086788;,-1.104504;2.080977;0.161627;,-0.776116;2.801809;0.161627;,-0.776116;2.801809;-0.086788;,-1.104504;2.080977;0.161627;,-1.279824;2.160847;0.161627;,-0.951436;2.881679;0.161627;,-0.776116;2.801809;0.161627;,-1.279824;2.160847;0.161627;,-1.279824;2.160847;-0.086788;,-0.951436;2.881679;-0.086788;,-0.951436;2.881679;0.161627;,-1.279824;2.160847;-0.086788;,-1.104504;2.080977;-0.086788;,-0.776116;2.801809;-0.086788;,-0.951436;2.881679;-0.086788;,-1.104504;2.080977;0.161627;,-1.104504;2.080977;-0.086788;,-1.279824;2.160847;-0.086788;,-1.279824;2.160847;0.161627;,-0.951436;2.881679;0.161627;,-0.951436;2.881679;-0.086788;,-0.776116;2.801809;-0.086788;,-0.776116;2.801809;0.161627;,-0.055633;0.093601;-0.190081;,-0.055633;0.093601;0.190081;,-0.055633;1.205294;0.190081;,-0.055633;1.205294;-0.190081;,-0.055633;0.093601;0.190081;,-0.43017;0.093601;0.190081;,-0.43017;1.205294;0.190081;,-0.055633;1.205294;0.190081;,-0.43017;0.093601;0.190081;,-0.43017;0.093601;-0.190081;,-0.43017;1.205294;-0.190081;,-0.43017;1.205294;0.190081;,-0.43017;0.093601;-0.190081;,-0.055633;0.093601;-0.190081;,-0.055633;1.205294;-0.190081;,-0.43017;1.205294;-0.190081;,-0.055633;0.093601;0.190081;,-0.055633;0.093601;-0.190081;,-0.43017;0.093601;-0.190081;,-0.43017;0.093601;0.190081;,-0.43017;1.205294;0.190081;,-0.43017;1.205294;-0.190081;,-0.055633;1.205294;-0.190081;,-0.055633;1.205294;0.190081;,0;3.790919;0.428464;,-0.43344;3.809586;0.409797;,-0.43344;4.560537;0.409797;,0;4.579204;0.428464;,-0.43344;3.809586;0.409797;,-0.43344;3.809586;-0.284975;,-0.43344;4.560537;-0.284975;,-0.43344;4.560537;0.409797;,0;3.790919;0.428464;,0;3.790919;-0.303642;,-0.43344;3.809586;-0.284975;,-0.43344;3.809586;0.409797;,-0.43344;4.560537;0.409797;,-0.43344;4.560537;-0.284975;,0;4.579204;-0.303642;,0;4.579204;0.428464;,-0.43344;3.809586;-0.284975;,0;3.790919;-0.303642;,0;4.579204;-0.303642;,-0.43344;4.560537;-0.284975;;68;4;3,2,1,0;,4;7,6,5,4;,4;11,10,9,8;,4;15,14,13,12;,4;19,18,17,16;,4;23,22,21,20;,4;27,26,25,24;,4;31,30,29,28;,4;35,34,33,32;,4;39,38,37,36;,4;43,42,41,40;,4;47,46,45,44;,4;51,50,49,48;,4;55,54,53,52;,4;59,58,57,56;,4;63,62,61,60;,4;67,66,65,64;,4;71,70,69,68;,4;75,74,73,72;,4;79,78,77,76;,4;83,82,81,80;,4;87,86,85,84;,4;91,90,89,88;,4;95,94,93,92;,4;99,98,97,96;,4;103,102,101,100;,4;107,106,105,104;,4;111,110,109,108;,4;115,114,113,112;,4;119,118,117,116;,4;123,122,121,120;,4;127,126,125,124;,4;131,130,129,128;,4;135,134,133,132;,4;139,138,137,136;,4;143,142,141,140;,4;147,146,145,144;,4;151,150,149,148;,4;155,154,153,152;,4;159,158,157,156;,4;163,162,161,160;,4;167,166,165,164;,4;171,170,169,168;,4;175,174,173,172;,4;179,178,177,176;,4;183,182,181,180;,4;187,186,185,184;,4;191,190,189,188;,4;195,194,193,192;,4;199,198,197,196;,4;203,202,201,200;,4;207,206,205,204;,4;211,210,209,208;,4;215,214,213,212;,4;219,218,217,216;,4;223,222,221,220;,4;227,226,225,224;,4;231,230,229,228;,4;235,234,233,232;,4;239,238,237,236;,4;243,242,241,240;,4;247,246,245,244;,4;251,250,249,248;,4;255,254,253,252;,4;259,258,257,256;,4;263,262,261,260;,4;267,266,265,264;,4;271,270,269,268;;MeshNormals{272;0;-0.707083;0.707083;,0;0.707083;0.707083;,0.577349;0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0;-0.707083;0.707083;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,0;-0.707083;-0.707083;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0.577349;0.577349;0.577349;,0;0.707083;0.707083;,0;0.707083;-0.707083;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0;0.707083;-0.707083;,0;-0.707083;-0.707083;,-0.286019;-0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.764733;0.286019;0.577349;,-0.286019;-0.764733;0.577349;,-0.286019;-0.764733;0.577349;,-0.764733;0.286019;0.577349;,0.286019;0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;0.577349;,0.286019;0.764733;0.577349;,0.286019;0.764733;-0.577349;,0.764733;-0.286019;-0.577349;,0.764733;-0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,-0.286019;-0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,0.286019;0.764733;0.577349;,-0.764733;0.286019;0.577349;,-0.764733;0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.286019;-0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.764733;0.286019;0.577349;,-0.286019;-0.764733;0.577349;,-0.286019;-0.764733;0.577349;,-0.764733;0.286019;0.577349;,0.286019;0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;0.577349;,0.286019;0.764733;0.577349;,0.286019;0.764733;-0.577349;,0.764733;-0.286019;-0.577349;,0.764733;-0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.764733;0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,-0.286019;-0.764733;0.577349;,0.764733;-0.286019;0.577349;,0.764733;-0.286019;-0.577349;,-0.286019;-0.764733;-0.577349;,0.286019;0.764733;0.577349;,-0.764733;0.286019;0.577349;,-0.764733;0.286019;-0.577349;,0.286019;0.764733;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0;-0.707083;0.707083;,0;0.707083;0.707083;,0.599902;0.565722;0.565722;,0.599902;-0.565722;0.565722;,0.599902;-0.565722;0.565722;,0.599902;0.565722;0.565722;,0.599902;0.565722;-0.565722;,0.599902;-0.565722;-0.565722;,0;-0.707083;0.707083;,0.599902;-0.565722;0.565722;,0.599902;-0.565722;-0.565722;,0;-0.707083;-0.707083;,0.599902;0.565722;0.565722;,0;0.707083;0.707083;,0;0.707083;-0.707083;,0.599902;0.565722;-0.565722;,0.599902;-0.565722;-0.565722;,0.599902;0.565722;-0.565722;,0;0.707083;-0.707083;,0;-0.707083;-0.707083;,0;-0.707083;0.707083;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0;0.707083;0.707083;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,0;-0.707083;0.707083;,0;-0.707083;-0.707083;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0.577349;0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0;0.707083;-0.707083;,0;0.707083;0.707083;,-0.577349;-0.577349;-0.577349;,0;-0.707083;-0.707083;,0;0.707083;-0.707083;,-0.577349;0.577349;-0.577349;,0.286019;-0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.764733;0.286019;0.577349;,0.764733;0.286019;-0.577349;,0.286019;-0.764733;0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,0.764733;0.286019;0.577349;,-0.764733;-0.286019;0.577349;,-0.764733;-0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,-0.286019;0.764733;0.577349;,-0.764733;-0.286019;-0.577349;,0.286019;-0.764733;-0.577349;,0.764733;0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.286019;-0.764733;-0.577349;,-0.764733;-0.286019;-0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,-0.286019;0.764733;-0.577349;,0.764733;0.286019;-0.577349;,0.764733;0.286019;0.577349;,0.286019;-0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.764733;0.286019;0.577349;,0.764733;0.286019;-0.577349;,0.286019;-0.764733;0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,0.764733;0.286019;0.577349;,-0.764733;-0.286019;0.577349;,-0.764733;-0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,-0.286019;0.764733;0.577349;,-0.764733;-0.286019;-0.577349;,0.286019;-0.764733;-0.577349;,0.764733;0.286019;-0.577349;,-0.286019;0.764733;-0.577349;,0.286019;-0.764733;0.577349;,0.286019;-0.764733;-0.577349;,-0.764733;-0.286019;-0.577349;,-0.764733;-0.286019;0.577349;,-0.286019;0.764733;0.577349;,-0.286019;0.764733;-0.577349;,0.764733;0.286019;-0.577349;,0.764733;0.286019;0.577349;,0.577349;-0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;0.577349;0.577349;,0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,0.577349;0.577349;0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,-0.577349;0.577349;0.577349;,-0.577349;-0.577349;-0.577349;,0.577349;-0.577349;-0.577349;,0.577349;0.577349;-0.577349;,-0.577349;0.577349;-0.577349;,0.577349;-0.577349;0.577349;,0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;-0.577349;,-0.577349;-0.577349;0.577349;,-0.577349;0.577349;0.577349;,-0.577349;0.577349;-0.577349;,0.577349;0.577349;-0.577349;,0.577349;0.577349;0.577349;,0;-0.707083;0.707083;,-0.599902;-0.565722;0.565722;,-0.599872;0.565722;0.565722;,0;0.707083;0.707083;,-0.599902;-0.565722;0.565722;,-0.599902;-0.565722;-0.565722;,-0.599872;0.565722;-0.565722;,-0.599872;0.565722;0.565722;,0;-0.707083;0.707083;,0;-0.707083;-0.707083;,-0.599902;-0.565722;-0.565722;,-0.599902;-0.565722;0.565722;,-0.599872;0.565722;0.565722;,-0.599872;0.565722;-0.565722;,0;0.707083;-0.707083;,0;0.707083;0.707083;,-0.599902;-0.565722;-0.565722;,0;-0.707083;-0.707083;,0;0.707083;-0.707083;,-0.599872;0.565722;-0.565722;;68;4;3,2,1,0;,4;7,6,5,4;,4;11,10,9,8;,4;15,14,13,12;,4;19,18,17,16;,4;23,22,21,20;,4;27,26,25,24;,4;31,30,29,28;,4;35,34,33,32;,4;39,38,37,36;,4;43,42,41,40;,4;47,46,45,44;,4;51,50,49,48;,4;55,54,53,52;,4;59,58,57,56;,4;63,62,61,60;,4;67,66,65,64;,4;71,70,69,68;,4;75,74,73,72;,4;79,78,77,76;,4;83,82,81,80;,4;87,86,85,84;,4;91,90,89,88;,4;95,94,93,92;,4;99,98,97,96;,4;103,102,101,100;,4;107,106,105,104;,4;111,110,109,108;,4;115,114,113,112;,4;119,118,117,116;,4;123,122,121,120;,4;127,126,125,124;,4;131,130,129,128;,4;135,134,133,132;,4;139,138,137,136;,4;143,142,141,140;,4;147,146,145,144;,4;151,150,149,148;,4;155,154,153,152;,4;159,158,157,156;,4;163,162,161,160;,4;167,166,165,164;,4;171,170,169,168;,4;175,174,173,172;,4;179,178,177,176;,4;183,182,181,180;,4;187,186,185,184;,4;191,190,189,188;,4;195,194,193,192;,4;199,198,197,196;,4;203,202,201,200;,4;207,206,205,204;,4;211,210,209,208;,4;215,214,213,212;,4;219,218,217,216;,4;223,222,221,220;,4;227,226,225,224;,4;231,230,229,228;,4;235,234,233,232;,4;239,238,237,236;,4;243,242,241,240;,4;247,246,245,244;,4;251,250,249,248;,4;255,254,253,252;,4;259,258,257,256;,4;263,262,261,260;,4;267,266,265,264;,4;271,270,269,268;;}MeshTextureCoords{272;0.849264;0.899246;,0.849264;0.931916;,0.861547;0.931916;,0.861547;0.899246;,0.916988;0.931916;,0.916988;0.899246;,0.9054;0.899246;,0.9054;0.931916;,0.84857;0.844707;,0.84857;0.83254;,0.836981;0.83254;,0.836981;0.844707;,0.927004;0.903587;,0.927004;0.931916;,0.937019;0.931916;,0.937019;0.903587;,0.937019;0.903587;,0.937019;0.931916;,0.946887;0.931916;,0.946887;0.903587;,0.888533;0.856954;,0.888533;0.828625;,0.878517;0.828625;,0.878517;0.856954;,0.939292;0.870917;,0.939292;0.899246;,0.949159;0.899246;,0.949159;0.870917;,0.946887;0.91117;,0.956719;0.91117;,0.956719;0.901213;,0.946887;0.901213;,0.865118;0.813135;,0.855286;0.813135;,0.855286;0.823092;,0.865118;0.823092;,0.866874;0.847426;,0.866874;0.835259;,0.855286;0.835259;,0.855286;0.847426;,0.598002;0.973516;,0.598002;0.206739;,0.309722;0.206739;,0.309722;0.973516;,0.909393;0.822135;,0.909393;0.841014;,0.915938;0.841014;,0.915938;0.822135;,0.951962;0.931916;,0.951962;0.91117;,0.946887;0.91117;,0.946887;0.931916;,0.948762;0.841801;,0.948762;0.822921;,0.942217;0.822921;,0.942217;0.841801;,0.893608;0.838075;,0.893608;0.817329;,0.888533;0.817329;,0.888533;0.838075;,0.900724;0.909292;,0.90515;0.909292;,0.90515;0.902786;,0.900724;0.902786;,0.953585;0.871994;,0.949159;0.871994;,0.949159;0.8785;,0.953585;0.8785;,0.84857;0.837995;,0.84857;0.856874;,0.855114;0.856874;,0.855114;0.837995;,0.902881;0.83746;,0.902881;0.816714;,0.897805;0.816714;,0.897805;0.83746;,0.942217;0.841801;,0.942217;0.822921;,0.935673;0.822921;,0.935673;0.841801;,0.949159;0.8785;,0.949159;0.899246;,0.954235;0.899246;,0.954235;0.8785;,0.919226;0.822135;,0.923651;0.822135;,0.923651;0.815629;,0.919226;0.815629;,0.928077;0.815629;,0.923651;0.815629;,0.923651;0.822135;,0.928077;0.822135;,0.865301;0.847426;,0.865301;0.876542;,0.875317;0.876542;,0.875317;0.847426;,0.909393;0.841014;,0.909393;0.87013;,0.919261;0.87013;,0.919261;0.841014;,0.855286;0.847426;,0.855286;0.876542;,0.865301;0.876542;,0.865301;0.847426;,0.919261;0.841014;,0.919261;0.87013;,0.929128;0.87013;,0.929128;0.841014;,0.878517;0.828625;,0.888349;0.828625;,0.88835;0.818668;,0.878517;0.818668;,0.836981;0.83254;,0.846814;0.83254;,0.846814;0.822583;,0.836981;0.822583;,0.857749;0.887894;,0.836981;0.887894;,0.837473;0.899246;,0.857257;0.899246;,0.855286;0.876542;,0.855286;0.856874;,0.836981;0.856874;,0.836981;0.876542;,0.897805;0.887893;,0.897313;0.876622;,0.879009;0.876622;,0.878517;0.887893;,0.886604;0.909292;,0.886112;0.920645;,0.9054;0.920645;,0.904908;0.909292;,0.977665;0.442421;,0.977665;0.131438;,0.799225;0.123708;,0.799225;0.450151;,0.849264;0.899246;,0.836981;0.899246;,0.836981;0.931916;,0.849264;0.931916;,0.909393;0.866576;,0.897805;0.866576;,0.897805;0.899246;,0.909393;0.899246;,0.84857;0.844707;,0.836981;0.844707;,0.836981;0.856874;,0.84857;0.856874;,0.929276;0.899246;,0.939292;0.899246;,0.939292;0.870917;,0.929276;0.870917;,0.876741;0.819096;,0.866874;0.819096;,0.866874;0.847426;,0.876741;0.847426;,0.939144;0.841801;,0.929128;0.841801;,0.929128;0.87013;,0.939144;0.87013;,0.949011;0.841801;,0.939144;0.841801;,0.939144;0.87013;,0.949011;0.87013;,0.836981;0.812626;,0.836981;0.822583;,0.846814;0.822583;,0.846814;0.812626;,0.909393;0.812178;,0.909393;0.822135;,0.919226;0.822135;,0.919226;0.812178;,0.866874;0.823092;,0.855286;0.823092;,0.855286;0.835259;,0.866874;0.835259;,0.021442;0.973516;,0.309722;0.973516;,0.309722;0.206739;,0.021442;0.206739;,0.916039;0.841014;,0.922583;0.841014;,0.922583;0.822135;,0.916039;0.822135;,0.907956;0.816714;,0.902881;0.816714;,0.902881;0.83746;,0.907956;0.83746;,0.929128;0.822135;,0.922583;0.822135;,0.922583;0.841014;,0.929128;0.841014;,0.853645;0.817249;,0.84857;0.817249;,0.84857;0.837995;,0.853645;0.837995;,0.900724;0.909292;,0.900724;0.902786;,0.895944;0.902786;,0.895944;0.909292;,0.93896;0.816415;,0.93896;0.822921;,0.94374;0.822921;,0.94374;0.816415;,0.935673;0.822921;,0.929128;0.822921;,0.929128;0.841801;,0.935673;0.841801;,0.954087;0.849384;,0.949011;0.849384;,0.949011;0.87013;,0.954087;0.87013;,0.895077;0.838075;,0.888533;0.838075;,0.888533;0.856954;,0.895077;0.856954;,0.948762;0.841801;,0.953838;0.841801;,0.953838;0.821055;,0.948762;0.821055;,0.94374;0.816415;,0.94374;0.822921;,0.94852;0.822921;,0.94852;0.816415;,0.949011;0.842878;,0.949011;0.849384;,0.953791;0.849384;,0.953791;0.842878;,0.919409;0.87013;,0.909393;0.87013;,0.909393;0.899246;,0.919409;0.899246;,0.897805;0.866576;,0.907672;0.866576;,0.907672;0.83746;,0.897805;0.83746;,0.927004;0.9028;,0.916988;0.9028;,0.916988;0.931916;,0.927004;0.931916;,0.929276;0.87013;,0.919409;0.87013;,0.919409;0.899246;,0.929276;0.899246;,0.93896;0.822921;,0.93896;0.812965;,0.929128;0.812965;,0.929128;0.822921;,0.886112;0.899336;,0.886112;0.909292;,0.895944;0.909292;,0.895944;0.899336;,0.857749;0.887894;,0.857257;0.876542;,0.837473;0.876542;,0.836981;0.887894;,0.896821;0.856954;,0.878517;0.856954;,0.878517;0.876622;,0.896821;0.876622;,0.897805;0.887893;,0.878517;0.887893;,0.879009;0.899246;,0.897313;0.899246;,0.886604;0.931916;,0.904908;0.931916;,0.9054;0.920645;,0.886112;0.920645;,0.620785;0.44242;,0.799225;0.450151;,0.799225;0.123708;,0.620785;0.131438;;}XSkinMeshHeader{3;9;10;}SkinWeights{"Armature_arm_l";24;44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,67,66;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;-0.047733,0.901521,0.430095,0,-0.097683,0.424309,-0.900233,0,-0.994073,-0.084983,0.06781,0,0.374873,-2.006904,2.980378,1;;}SkinWeights{"Armature_elbow_r";24;216,219,218,213,212,215,214,209,224,208,227,211,226,210,206,221,207,220,204,223,205,222,225,217;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;0.102316,0.92166,-0.374266,0,-0.090709,-0.366028,-0.926173,0,-0.990608,0.128712,0.046152,0,0.402018,1.853661,2.350172,1;;}SkinWeights{"Armature_arm_r";24;186,187,184,185,182,183,180,194,195,203,202,192,193,201,200,199,190,198,191,197,188,196,189,181;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;-0.047733,0.901521,-0.430095,0,-0.05233,-0.432251,-0.900234,0,-0.997489,-0.020464,0.067809,0,0.160852,2.035269,2.980378,1;;}SkinWeights{"Armature_knee_l";24;105,99,114,106,98,115,107,101,93,108,100,92,109,103,95,110,102,94,111,97,112,104,113,96;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,0,0,0,0,0.054357,-0.998522,0,0,0.998501,0.054355,0,-0.246294,-0.008592,1.301673,1;;}SkinWeights{"Armature_Bone_007";40;132,133,134,135,124,125,126,252,253,254,255,121,122,264,265,123,267,268,269,270,116,256,258,259,260,261,262,263,271,266,120,119,117,128,129,127,130,118,131,257;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,0,0,0,0,0,1,0,0,-1,0,0,0,0,-3.793003,1;;}SkinWeights{"Armature_elbow_l";24;88,80,72,91,83,75,90,82,74,70,85,77,71,84,76,68,87,79,69,86,78,89,81,73;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;0.102316,0.92166,0.374266,0,-0.008222,0.377011,-0.926173,0,-0.994719,0.091686,0.046152,0,-0.014321,-1.896701,2.350171,1;;}SkinWeights{"Armature_knee_r";24;249,235,250,234,251,229,244,228,245,231,246,230,247,240,241,242,243,237,236,239,238,233,248,232;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1;1,0,0,0,0,0.054357,-0.998522,0,0,0.998501,0.054355,0,0.246294,-0.008592,1.301673,1;;}SkinWeights{"Armature_leg_l";38;0,3,4,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,40,43,136,145,177,144;0.055873,0.852304,0.852304,0.82998,0.055873,0.852304,0.82998,0.054606,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0.82998,0.054606,0.055873,0.054606,0.054606,0.055873;1,0,0,0,0,-0.056452,-0.998405,0,0,0.998385,-0.056452,0,-0.246294,0.135476,2.396023,1;;}SkinWeights{"Armature_leg_r";38;0,170,169,11,168,151,150,149,148,147,146,176,145,177,144,159,158,157,156,155,154,153,167,136,166,137,165,164,163,140,162,141,161,43,160,152,8,171;0.055873,1,1,0.054606,1,1,1,1,1,0.852304,0.82998,0.82998,0.054606,0.054606,0.055873,1,1,1,1,1,1,1,1,0.055873,1,0.852304,1,1,1,0.852304,1,0.82998,1,0.054606,1,1,0.055873,1;1,0,0,0,0,-0.056452,-0.998405,0,0,0.998385,-0.056452,0,0.246294,0.135476,2.396023,1;;}SkinWeights{"Armature_body";40;0,1,2,3,4,5,6,7,8,9,10,11,36,37,38,39,40,41,42,43,136,137,138,139,140,147,141,146,142,145,143,144,179,174,178,173,177,172,176,175;0.888255,1,1,0.147696,0.147696,1,1,0.17002,0.888255,0.147696,0.17002,0.890788,1,1,1,1,0.17002,1,1,0.890788,0.888255,0.147696,1,1,0.147696,0.147696,0.17002,0.17002,1,0.890788,1,0.888255,1,1,1,1,0.890788,1,0.17002,1;1,0,0,0,0,0,1,0,0,-1,0,0,0,0,-2.571201,1;;}}}}} \ No newline at end of file diff --git a/irr/media/cooltexture.png b/irr/media/cooltexture.png deleted file mode 100755 index fcc219ac7ea95c2ad9b49ad0064835638cb51c69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4828 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 diff --git a/irr/scripts/ci-build-android.sh b/irr/scripts/ci-build-android.sh deleted file mode 100755 index a865e56a4..000000000 --- a/irr/scripts/ci-build-android.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash -e - -# NOTE: this code is mostly copied from minetest_android_deps -# - -png_ver=1.6.40 -jpeg_ver=3.0.1 - -download () { - get_tar_archive libpng "https://download.sourceforge.net/libpng/libpng-${png_ver}.tar.gz" - get_tar_archive libjpeg "https://download.sourceforge.net/libjpeg-turbo/libjpeg-turbo-${jpeg_ver}.tar.gz" -} - -build () { - # Build libjpg and libpng first because Irrlicht needs them - mkdir -p libpng - pushd libpng - $srcdir/libpng/configure --host=$CROSS_PREFIX - make && make DESTDIR=$PWD install - popd - - mkdir -p libjpeg - pushd libjpeg - cmake $srcdir/libjpeg "${CMAKE_FLAGS[@]}" -DENABLE_SHARED=OFF - make && make DESTDIR=$PWD install - popd - - local libpng=$PWD/libpng/usr/local/lib/libpng.a - local libjpeg=$(echo $PWD/libjpeg/opt/libjpeg-turbo/lib*/libjpeg.a) - cmake $srcdir/irrlicht "${CMAKE_FLAGS[@]}" \ - -DBUILD_SHARED_LIBS=OFF \ - -DPNG_LIBRARY=$libpng \ - -DPNG_PNG_INCLUDE_DIR=$(dirname "$libpng")/../include \ - -DJPEG_LIBRARY=$libjpeg \ - -DJPEG_INCLUDE_DIR=$(dirname "$libjpeg")/../include - make - - cp -p lib/Android/libIrrlichtMt.a $libpng $libjpeg $pkgdir/ - cp -a $srcdir/irrlicht/include $pkgdir/include - cp -a $srcdir/irrlicht/media/Shaders $pkgdir/Shaders -} - -get_tar_archive () { - # $1: folder to extract to, $2: URL - local filename="${2##*/}" - [ -d "$1" ] && return 0 - wget -c "$2" -O "$filename" - mkdir -p "$1" - tar -xaf "$filename" -C "$1" --strip-components=1 - rm "$filename" -} - -_setup_toolchain () { - local toolchain=$(echo "$ANDROID_NDK"/toolchains/llvm/prebuilt/*) - if [ ! -d "$toolchain" ]; then - echo "Android NDK path not specified or incorrect"; return 1 - fi - export PATH="$toolchain/bin:$ANDROID_NDK:$PATH" - - unset CFLAGS CPPFLAGS CXXFLAGS - - TARGET_ABI="$1" - API=21 - if [ "$TARGET_ABI" == armeabi-v7a ]; then - CROSS_PREFIX=armv7a-linux-androideabi - CFLAGS="-mthumb" - CXXFLAGS="-mthumb" - elif [ "$TARGET_ABI" == arm64-v8a ]; then - CROSS_PREFIX=aarch64-linux-android - elif [ "$TARGET_ABI" == x86 ]; then - CROSS_PREFIX=i686-linux-android - CFLAGS="-mssse3 -mfpmath=sse" - CXXFLAGS="-mssse3 -mfpmath=sse" - elif [ "$TARGET_ABI" == x86_64 ]; then - CROSS_PREFIX=x86_64-linux-android - else - echo "Invalid ABI given"; return 1 - fi - export CC=$CROSS_PREFIX$API-clang - export CXX=$CROSS_PREFIX$API-clang++ - export AR=llvm-ar - export RANLIB=llvm-ranlib - export CFLAGS="-fPIC ${CFLAGS}" - export CXXFLAGS="-fPIC ${CXXFLAGS}" - - CMAKE_FLAGS=( - "-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake" - "-DANDROID_ABI=$TARGET_ABI" "-DANDROID_NATIVE_API_LEVEL=$API" - "-DCMAKE_BUILD_TYPE=Release" - ) - - # make sure pkg-config doesn't interfere - export PKG_CONFIG=/bin/false - - export MAKEFLAGS="-j$(nproc)" -} - -_run_build () { - local abi=$1 - irrdir=$PWD - - mkdir -p $RUNNER_TEMP/src - cd $RUNNER_TEMP/src - srcdir=$PWD - [ -d irrlicht ] || ln -s $irrdir irrlicht - download - - builddir=$RUNNER_TEMP/build/irrlicht-$abi - pkgdir=$RUNNER_TEMP/pkg/$abi/Irrlicht - rm -rf "$pkgdir" - mkdir -p "$builddir" "$pkgdir" - - cd "$builddir" - build -} - -if [ $# -lt 1 ]; then - echo "Usage: ci-build-android.sh " - exit 1 -fi - -_setup_toolchain $1 -_run_build $1 diff --git a/irr/scripts/ci-build-mingw.sh b/irr/scripts/ci-build-mingw.sh deleted file mode 100755 index b1fdd7b99..000000000 --- a/irr/scripts/ci-build-mingw.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -e -topdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -[[ -z "$CC" || -z "$CXX" ]] && exit 255 -variant=win32 -[[ "$(basename "$CXX")" == "x86_64-"* ]] && variant=win64 -with_sdl=0 -[[ "$extras" == *"-sdl"* ]] && with_sdl=1 -#with_gl3=0 -#[[ "$extras" == *"-gl3"* ]] && with_gl3=1 - -libjpeg_version=3.0.1 -libpng_version=1.6.40 -sdl2_version=2.28.5 -zlib_version=1.3.1 - -download () { - local url=$1 - local filename=${url##*/} - local foldername=${filename%%[.-]*} - - [ -d "./$foldername" ] && return 0 - [ -e "$filename" ] || wget "$url" -O "$filename" - sha256sum -w -c <(grep -F "$filename" "$topdir/sha256sums.txt") - unzip -o "$filename" -d "$foldername" -} - -libs=$PWD/libs -mkdir -p libs -pushd libs -libhost="http://minetest.kitsunemimi.pw" -download "$libhost/llvm/libjpeg-$libjpeg_version-$variant.zip" -download "$libhost/llvm/libpng-$libpng_version-$variant.zip" -[ $with_sdl -eq 1 ] && download "$libhost/llvm/sdl2-$sdl2_version-$variant.zip" -download "$libhost/llvm/zlib-$zlib_version-$variant.zip" -popd - -tmp=( - -DCMAKE_SYSTEM_NAME=Windows \ - -DPNG_LIBRARY=$libs/libpng/lib/libpng.dll.a \ - -DPNG_PNG_INCLUDE_DIR=$libs/libpng/include \ - -DJPEG_LIBRARY=$libs/libjpeg/lib/libjpeg.dll.a \ - -DJPEG_INCLUDE_DIR=$libs/libjpeg/include \ - -DZLIB_LIBRARY=$libs/zlib/lib/libz.dll.a \ - -DZLIB_INCLUDE_DIR=$libs/zlib/include -) -if [ $with_sdl -eq 1 ]; then - tmp+=( - -DUSE_SDL2=ON - -DCMAKE_PREFIX_PATH=$libs/sdl2/lib/cmake - ) -else - tmp+=(-DUSE_SDL2=OFF) -fi -#[ $with_gl3 -eq 1 ] && tmp+=(-DENABLE_OPENGL=OFF -DENABLE_OPENGL3=ON) - -cmake . "${tmp[@]}" -make -j$(nproc) - -if [ "$1" = "package" ]; then - make DESTDIR=$PWD/_install install - # strip library - "${CXX%-*}-strip" --strip-unneeded _install/usr/local/lib/*.dll - # bundle the DLLs that are specific to Irrlicht (kind of a hack) - shopt -s nullglob - cp -p $libs/*/bin/{libjpeg,libpng,SDL}*.dll _install/usr/local/lib/ - # create a ZIP - (cd _install/usr/local; zip -9r "$OLDPWD/irrlicht-$variant$extras.zip" -- *) -fi -exit 0 diff --git a/irr/scripts/ci-get-mingw.sh b/irr/scripts/ci-get-mingw.sh deleted file mode 100755 index 9cf933fea..000000000 --- a/irr/scripts/ci-get-mingw.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -e -topdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -name=llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz -wget "https://github.com/mstorsjo/llvm-mingw/releases/download/20231128/$name" -O "$name" -sha256sum -w -c <(grep -F "$name" "$topdir/sha256sums.txt") -sudo tar -xaf "$name" -C /usr --strip-components=1 -rm -f "$name" diff --git a/irr/scripts/sha256sums.txt b/irr/scripts/sha256sums.txt deleted file mode 100644 index 0c080de3b..000000000 --- a/irr/scripts/sha256sums.txt +++ /dev/null @@ -1,9 +0,0 @@ -0f21ff3be90311092fe32e0e30878ef3ae9d9437b8d9ac25ef279e0d84e9bb8e llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz -53dfd31285f470fcf0dca88217c5cf9c557729af6d103afae5936e72ddc38d3c libjpeg-3.0.1-win32.zip -3d44e0740914e6878300e30653aad39e974821b5d7f6c2567e246b4eb04a5324 libjpeg-3.0.1-win64.zip -6baf4e819bfb3573760524b5dc9a04b5e479090d6d2046b86cf39a3107c0071f libpng-1.6.40-win32.zip -c02e029f01fce44baea7f4aecfd2564bd8a03507c0c6af8b03339ae0452c8b7d libpng-1.6.40-win64.zip -f9f890af960e92fd3f532f2e9ac00681c33bc67a722e000dfdaeb41b0064f1a0 sdl2-2.28.5-win32.zip -8dde2c6963544b7d8a2e87c128ebbdf51ad0e70c7e2df986ff4e963ce9996d9b sdl2-2.28.5-win64.zip -8af10515d57dbfee5d2106cd66cafa2adeb4270d4c6047ccbf7e8b5d2d50681c zlib-1.3.1-win32.zip -ad43f5d23052590c65633530743e5d622cc76b33c109072e6fd7b487aff56bca zlib-1.3.1-win64.zip diff --git a/irr/test/CMakeLists.txt b/irr/test/CMakeLists.txt deleted file mode 100644 index 6518f69c1..000000000 --- a/irr/test/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -link_libraries(IrrlichtMt::IrrlichtMt) -add_executable(image_loader_test image_loader_test.cpp) - -function(test_image_loader format expected input) - string(TOLOWER ${format} suffix) - add_test(NAME ImageLoader${format}-${input} COMMAND image_loader_test ${expected} data/sample_${input}.${suffix} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -endfunction() - -test_image_loader(BMP 16color-16bpp 4bpp_v3) -# test_image_loader(BMP 16color-16bpp 4bpp_v7) -# test_image_loader(BMP 16color-16bpp 4bpp_v3_rle) -# test_image_loader(BMP 16color-16bpp 4bpp_v7_rle) -test_image_loader(BMP 30color-16bpp 8bpp_v3) -# test_image_loader(BMP 30color-16bpp 8bpp_v7) -test_image_loader(BMP 30color-16bpp 8bpp_v3_rle) -# test_image_loader(BMP 30color-16bpp 8bpp_v7_rle) -# test_image_loader(BMP 30color-16bpp 16bpp_v3) -# test_image_loader(BMP 30color-16bpp 16bpp_v7) -test_image_loader(BMP 30color-24bpp 24bpp_v3) -test_image_loader(BMP 30color-24bpp 24bpp_v7) - -test_image_loader(PNG 30color-24bpp 8bpp) -test_image_loader(PNG 30color-24bpp 24bpp) - -test_image_loader(TGA 30color-32bpp 8bpp_up) -test_image_loader(TGA 30color-32bpp 8bpp_down) -# test_image_loader(TGA 30color-16bpp 8bpp_rle_up) -# test_image_loader(TGA 30color-16bpp 8bpp_rle_down) -test_image_loader(TGA 30color-24bpp 24bpp_up) -test_image_loader(TGA 30color-24bpp 24bpp_down) -test_image_loader(TGA 30color-24bpp 24bpp_rle_up) -test_image_loader(TGA 30color-24bpp 24bpp_rle_down) diff --git a/irr/test/data/sample_16bpp_v3.bmp b/irr/test/data/sample_16bpp_v3.bmp deleted file mode 100644 index 4b6fbcf501a0fb43d42dc6ab2d6a2b67acb8a0e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 654 zcmZ{iF=~ZC5QaCoKmq~VEz+b)nc~4)!OkL<5*B3Om6mqy%FHdEC$SVyky^+_HZ!|f zw28}1p&$Ru|F6~O&!dXZ2lqFw7p^C+2ldQdsW1Ni`kpGj;HIs%We={y2|bQD{zEDi>Xk$h2^`XdhGq;dDQ^AUlO+ocbJxVJk@Kh{C$n3hIGLY0B+)aZ z`HZZ$;64-{ZlVW`aHUyE6cg96-pI^8EV%V#TQ5}RLanVK@uGP{&z!hkFma`UN+~j> z3AK=Af4H7wK84Tu2nSWT3YT&Ocg|7VNRmoz7cxlfz-kIEIB@&0^^&(Zfac)XK~iLP zAHZ$>#LS4$95*9+NMvMkaGC4=IH(p(J7z8_My}*EI@S`sv?9vxe z@1!`+QmDlVBY*sN|1X!XKev#cPuY8^N2zOc;by*8^p)uvh}HEo|H>Su~65W6Eb0=6saHGH#U1IikPLQQc^g z0OOi1u{dysqmLf_)Q*kjtq!nSIB}2_g`HaTW77-M5mw91s2(v}?Hv8g)n^XNf%1$q vmlP9MNE)?hsorwe%8hGHR}|A^;(4!RAS`8Un{Os_%1S!P^^(r@-vjUq7yl1M diff --git a/irr/test/data/sample_24bpp.png b/irr/test/data/sample_24bpp.png deleted file mode 100644 index d38f5989c712b9edb7992dd8f84b78a2a962420a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 580 zcmV-K0=xZ*P);t$FE~(5E9%T9 zp8{gF0Uiu{H{aNfAR>SFUL78mO;lMkQQvsH-FeCVxTmSEyRTzHK*v(Ob(Lkea*hmi z0`$Al$^G&u5W;YDgp3K(=|&42Nz~&0TDBnVI+ThvYPu+?b?)Xp<(b8o>G6Cc2ogYJ zRVw?b`0CKsTawjYyhU@HFtM`Mk;@klxX38fA>0D~!VjsR}z+dq~9&4Dz4H3rxW zb|(zO05Ya35W*tmT~QEN1L#DB+ZSuVki+=tAoQkfq#0&d>Ky?jSq7F?&q$A%Uph7W zWNfZ@B68@;+>XeYV`QZx?Pnb1DvAVlEdO0<@44BsR zWkW?>(aG1|z% S%MUvM0000sbf}{zguo6k3 zVrRNEqJelxcKpts*>N!%4$HFP+y8QAjhQm~%b01CF*CFFZ0l!A1smjT5ZYqX?mjS4 zdGK-&Js*xAYvY@v(e?4@r2ci<{5MyS>>YV>TqD`2HR42d*G*Mh=tA<-JpMazQADV?_l? z=RuzixvQRl_{8n-RuBOZC24t}VYL%*+%ize3Fy;lnQ@;&l?Rh$(kGM$t!yFua|KD4#O&kTcrqyTj>PDrGj)4gTfY@B*h^(Wx^r{$Oy$|6VNFJ zc~5x|NDN`+tcNU-CDD$Rh!D%bV+Qc z+VAgtyzcqW2d&3^>*la|b<{jAe4muQ&MKb*AkB&$a74N87i+d~4`cVQZLSCf;_q&X z&98(NI=^cS1HC`_+~X$&Q8^Z{P1)@nBCfqI$ap|@5|-;q5)m;zcqMr>LLsBMc<3rS zzEqaTy&$s%U@#lL%UUCflBDFV-#P|Tm^6w}r`1Nlg}h-Pba0X1@lsg+M)(|C|hdKg0Sn*(jpO&kr0H7;LsG& zP|=oKOH`nh-qZJZt|u{v%N?H2`905bu1dvK_cyK*YC?(5l;e8$G1&WjI|yD5+fRk| z%~9+6xOGzeIW2#m*S;?9nwhJ5(^LwT5mzb{dVQ5!R3>yf`~Iq_r~KnN0Py7O#;vAY zR~qhMUA*rc?U$6!`J+0$?jSD~(ow4xLfaG_S*ZPz;3LinK?5+71T;*C#G!&xol$hY zN;!%!UXZ3Vm+34FV7G^{+e0y=06oVL!T$Du zF07eEqzacAb;~q?IE52BRpgnlCjdJ)aAptPJRBMVM3D$FE6w?|=`InOu+j|J>6Ely$xfZ*^qv{R z$iFGOkTD?!=6oAT$GxdSWao}eVx?gRGb&%Tkne^p;pm8U%()4QatC3T_KO~Jm+=4N Z@BUjB=a-VpsfE7CeZUl+5g^f)R2}WEH#3qo!O2k6N z&U7}SfkaJqJa_Nx7?Z+qS++TQzWdE22&RJUHxo<;vw^L|GP{pSwflCEyc`anqT$Wa z;OcmAQvW`Uzs}m9=Xd?f&iHt6lm+~kwdK9{SF6&G=L9cLyuDkC)L!?z)Yk9uTGNyX zjV+fv$cuuc9kd;&fl2E@jqO21YQzP_kQO6g((JIQt+kvnH1e{ug-E3yq$LGG7+7>9 zym;sn(iu~bO<5C{Rs^N>LTSCiW+1L)f7?byUIa{8z_EG(l_f2M)ET3q;N_A7vooco z$up&4KxJ*CfRybdXC)>ig)o5RWHmidcLbrKUJptqnN+|^ES*%lP)I?f-q7f|_=|)Q zP&U=H=te;5^&lUI^_zOoNi$GdGED(PqeiXh_ad=lqd-CliInZq)dZ}yl;tAHXGs+m pHLzTy6m(BHm#p5u=Q8=1$^K+(E9<$9{AzxAX>M~NznGg3egMO@v-AJ} diff --git a/irr/test/data/sample_24bpp_v3.bmp b/irr/test/data/sample_24bpp_v3.bmp deleted file mode 100644 index 4684f14669f8f4a1601a63dcb70a92ed998200fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 954 zcmaizF-yZh7=|M{>)_(<;^gQLaQDY_3w7vXTy!uMq+SLWmm*TE7!bsz(7{Q>LB-8( zE~16Dn&y1Ie91MogK~F-!jtFy-iw=Ciwj=%x52r_US=<`=e!k8&)b>zyw!Ex*$*5x zh-K|Rr}h5FQTlc~eu>6+C&|rea@PDgkH0Uvqsxckb#Ho=*)k6MFD`8D;Pc(8`s+2t z&WU|+Z&BOZfm7S&BX(=rvO<%wRmbw6EU}Yr&T4XEJ67|TS#;{f2*@HfUf8tVV`|&z zgA)zgr)6K}>cH6vrx#sIZ&KrLMGf<>1e?us|^c@{Rq zVW$d*Rh-x?)Dv&y^AcOD=4^4-ls0?UbPlVycv}_JOe%qcn6&i6f+a9x>P73lFf4_a zS*fT{3me2zQN0g{8cGTejb4`f%+w1jQ)_IbC6>Y+%lpCI)MYkT7p#rknQW1-$(h=*(ilrFt0?^MWzGI`!Tti@bg?i1 diff --git a/irr/test/data/sample_24bpp_v7.bmp b/irr/test/data/sample_24bpp_v7.bmp deleted file mode 100644 index 1fa343188bc7deadf985d9ea5d274a1363e35c11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1038 zcma)*J4*vW6otn}t%Z%fy+0z2m5u)-*n}vgF&1j!Mv%B$*w_S-V8nnRHh~mYA{Huk z+S!N(5;fWJyK{FZYf@;qGfW=eoO93J}ygv!mXrjQ=$Mg=ahrDDRSvnI6hS(7=e;-Xv6 zBTc7bxZK1o`B1PJrcE8R&V^iTTGY>9?N8s<=g(PF QQ8KJF@ZZB4|8v3q0IVCfB>(^b diff --git a/irr/test/data/sample_4bpp_v3.bmp b/irr/test/data/sample_4bpp_v3.bmp deleted file mode 100644 index 0cc06f3161f8e3f81cd682e77503e8d1d8b594ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 298 zcmZ?r)na4-gEAng0mQ;U%n!tj3@kvBfnf^}3v)xT0FVvB|9jdP{Ib5hL4Z_ zGYFcccg$LI?%lfw3=D$CW)+=Kw!HAS?>BFqiwgzX!EX#??^?4j4ycA-{@dLfxAw$^ zg4iEFU%Rn;bsQgv&3HMteCMhd1_cHN1wmo6^qI5HfXq~21hOk;tpc%~{>tvoj-1L0 zV*4}5N~e}KtU3c?D~L;Hm$qtw*$mv$rl}3H)&Rv_?sD%o4Qo(Z1!B7ZotIY8x#|D| F0|4G7a;N|R diff --git a/irr/test/data/sample_8bpp.png b/irr/test/data/sample_8bpp.png deleted file mode 100644 index c81f816f4f031b4596304b336d80942b8d598b48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 346 zcmV-g0j2(lP)++atplo7P@c)0aK|9UknLF9QaZMnUG3b{yq`d9m91+wK&Byvx&U4-laBY@Q zRWa7FSO+(^Q&Fv0Vn|l&kxCmaynv`FY;1yv7%?D- zjgZ1h#6rc+bT*h+fMqj*L$k=ZVtMES-5c{ zBI1M$1SA{5g)5OPG-RhcHxdO$9lKI9NG{yX$KjrHE)Wmk1!MpcVzd0Eo}1BgU-ZIk zx%^qrclE}!{<^Lg&-Hpo?|sm#hvm}k^-6iM_Vjz>>3FlRw|^aM^>?-(%g*I)_d)-> z+dsPRAL(zWy}J|r{j7KMr~Gl*`+KE7dKLx3(B;Cf)?^sf8|Y!`K@taUh5I!NZ5q|H z$SZ6M5*EAMcce4WWzsMphKa!E(y=0JK+^~B4IJo{?b$`nQcP?S2QuX7(5$H} zbC4hd7X=$yV;!5Ih)t1zNo1Ks56o${eb7$tx-r2qLv9q(YbJu^6{0oHzNg#>h->?%?$RROJQbYwYTBNZN z5pg98fsiI(kt&1~CfI3bBPzH_*10*mMNawfa(LczfCT_k&<2_mG)S;3%Xa0hef_-J zcx6`yc4x`1ZQD0L?RMWDF50ax)%xw#W_AAV{@2d^*Y_j4_v6#_)uw<&hJkVK^wrWOt1~_&-QK}-$HKrnoWXD6Jc$bO7 zS9!gW#+V3#++i+91|;B+fW{aSY(WQ&<2t}lfR9Q{OtFa_FmB+Mg~%~;RT}A7qQ~1E zlLRHDbCIg3F10sqWpq%wz_Kw5i8+(K6r{ZbeHnV%o3zD%+#yRNzb3x=h>6F>G|H%-s49P7G`?0(+}Yva>r}H diff --git a/irr/test/data/sample_8bpp_up.tga b/irr/test/data/sample_8bpp_up.tga deleted file mode 100644 index a314e8689590abe043c1c5a60eb908aa77de8fe6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmYk(Jxjwt7zgmn`=yukl3bH(lB?f9zeRB!|fM`3oxD*kwVnGlW zp@Wl%gNmEoTttOdZF62y2mj-b-LhEb_hUWc%}NH+k6~JkdF*CipXMUR(|*(=?A2lot`VtS7s_xlcNjcm5K5g_yu3qUMc_p diff --git a/irr/test/data/sample_8bpp_v3.bmp b/irr/test/data/sample_8bpp_v3.bmp deleted file mode 100644 index a890e64655127621d12b5418e1799341e990ad14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 474 zcmYk2y)Oh&6vfYczwu^wzIOJRomq`WA^r%xKSn1MDjEtOiLeTdMg*}!A`y*5p%PIb zIy#LYvDlsGzFCEvoSVE~PTsxej!%r~C~Kek2q`A{Bn^x@FscE=V|@+LTS?ANla?SC zW>BooL(ZK*Zcafiw;&hqAzN+8gGtEs3lu9KcwH;-w9&)EW)D|eUC5nVoNafpyYqvh z{fxbnZ^-jc9A18*dns{r)4}Jp#PMAR?~esepF4PakuXh%dzGpmMz#82UY24C`u`xb zrs-rntE6E<#vo_R28cCzRuy3q)fCHf#j=d2LikA(*9UTD=g^x3dFly2j-!AzZ1U+% z%}gCGD%CiQB4WA$OV_l&(P0?X>kaZ*1~j!J@9$V^G;+9BmYO0-cpUg4v+Y5}GBOG? z=0Sz|b{U441uap`BW9={6*EB0VV>>VWoo#t5J=*{8DL)3uq+Bg%yo4dU0$Mp9{d7t CQCp}0 diff --git a/irr/test/data/sample_8bpp_v3_rle.bmp b/irr/test/data/sample_8bpp_v3_rle.bmp deleted file mode 100644 index d460d0fd66c87c67af2e4d3f302a7a44b1bdec51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 524 zcmXw$y-wUf6orqy{u!@-#`dgtc6Zh^X{dOJ^mzruE2N7^k%}}>Btb%CDbmm&1SlYo zkWiXP6saOa0Z}^WXi$R0CfW7mj=hX@Mw(A&=AL`!`HLkR_1dR>o+L<|WPy=@McSS% zG@7Hck$$sGdI!Dw8rAw+=#>-btyj?RN6>56(4#}>gO|{oUr~K{!2L#rn~zic*qY+o zrwR1VCC;`d*xmVu>hKnOCx4;8JmT~DA56|QjxNS{{H}3)HOB9s6;5x*_;sg&*m3+Y ziW8pYoo)}_kisOy4CD=T4HH)B_$pL!KP$+X4jJo<;DI0h*zg=2R-w$P+h%C>E+pYQ?*S4%#oGi<`-NDT7wqTj; zVUxWx7&LVQSM?oP6ro6Y#yqe0#1&nTjtJA3anCe6^D7rtVR%HmWh&Hl14R{x`z-Rf i*G$Ls6$DbE5UJm0QPOfTpr5#bFeY=>{xa(`Kc^Flsbf zlxG>x)E)Wsj`db6hihf2DUyW8fgdv4ZYs+tQJ66gs?4{`X2>jPi87DOP=8crfXrc@ g?b~J7a9tsg#DP;`Ud^y9YKF{pbt+xH#PoUa1n?(co&W#< diff --git a/irr/test/data/sample_8bpp_v7_rle.bmp b/irr/test/data/sample_8bpp_v7_rle.bmp deleted file mode 100644 index d197b09a615ea5b00dc78ab57a7f2dc44d43fed9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 608 zcmb7?KTi}v7{;I7+ds4HpV{5Hz2RHbhLQppgPFF$x6@4Kc)c zMiLTZV?tqNj0F-p>1Yg)aKf?UJG%=#lX)jIzdSSV^S+sx$8$F7VOzjU+6f9yVS!Nq zPicAhsEG%3HUa3{S)XQRpFFQO_gLDrcQ^Fn3+TmF=vNDul$DI z7(st{2L1LMs`U%}eN*AjyBi#B-r)Pz1bXKezHCqMe&-t1=mfiaSI}QC@$ujvCI=dy zevWbZL*w(|80W_o_D{w*JEb~e$MM4`PI#7gd(-fSWGNwLAa9^+n6OI6SD}jgSwX^d z$e3r`3lJ|umB_5fyM_;qyyi-UeJOd7i{8|b)lWm%W$6%)Bo`u2*}!Y{c4;|%Ka_DI zvOFhlcj|Xs+p=0aS(f*DgI4c$U>WaWc1X7|9sXxV{q~p3&G+`$Ix5-f5x2Bo& GiT(q@QD6!H diff --git a/irr/test/image_loader_test.cpp b/irr/test/image_loader_test.cpp deleted file mode 100644 index 1dd4888f2..000000000 --- a/irr/test/image_loader_test.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include -#include -#include -#include -#include - -using namespace irr; - -struct ImageDesc -{ - const char *name; - video::ECOLOR_FORMAT format; - std::vector data; -}; - -template -std::vector to_byte_array(std::initializer_list in_data) -{ - std::vector data; - data.resize(sizeof(T) * in_data.size()); - memcpy(data.data(), in_data.begin(), data.size()); - return data; -} - -static const ImageDesc test_images[] = { - {"16color-16bpp", video::ECF_A1R5G5B5, to_byte_array({ - 0xb6df, 0xb6df, 0xb6df, 0xb6df, 0xc23f, 0xc23f, 0xd61f, 0xd61f, 0xea3f, 0xea3f, 0xfe5e, 0xfe5e, 0xfe7c, 0xfe7a, 0xfe7a, 0xfe97, 0xfeb5, 0xfeb5, 0xff16, - 0xb6df, 0xb6df, 0xff3f, 0xff3f, 0xc23f, 0xf2df, 0xf2df, 0xf2df, 0xea3f, 0xda7f, 0xda7f, 0xfe5e, 0xfe7a, 0xc23f, 0xbe7f, 0xbe7f, 0xfeb5, 0xfeb5, 0xfb76, - 0xb6df, 0xff9e, 0xc23f, 0xc23f, 0xc23f, 0xf2df, 0xd61f, 0xf2df, 0xea3f, 0xda7f, 0xfe5e, 0xda7f, 0xfe7a, 0xc23f, 0xfe97, 0xfeb5, 0xfeb5, 0xff16, 0xfb76, - 0xb6df, 0xff9e, 0xbe7f, 0xc23f, 0xd61f, 0xfe7c, 0xd61f, 0xf2df, 0xfe5e, 0xf2df, 0xfe7c, 0xda7f, 0xfe7a, 0xda7f, 0xbe7f, 0xfeb5, 0xff16, 0xff16, 0xfb76, - 0xb6df, 0xff9e, 0xc23f, 0xc23f, 0xd61f, 0xff3f, 0xd61f, 0xf2df, 0xfe5e, 0xda7f, 0xfe7c, 0xda7f, 0xfe7a, 0xc23f, 0xfeb5, 0xfeb5, 0xff16, 0xff16, 0xfb76, - 0xb6df, 0xbe7f, 0xff9e, 0xff3f, 0xd61f, 0xff3f, 0xf2df, 0xf2df, 0xfe5e, 0xf2df, 0xda7f, 0xfe97, 0xfe97, 0xda7f, 0xc23f, 0xda7f, 0xfb76, 0xfb76, 0xfb76, - 0xbe7f, 0xc23f, 0xc23f, 0xd61f, 0xd61f, 0xea3f, 0xea3f, 0xfe5e, 0xfe5e, 0xfe7c, 0xfe7c, 0xfe7a, 0xfe97, 0xfeb5, 0xfeb5, 0xfeb5, 0xfb76, 0xfb76, 0xfb76, - 0xbe7f, 0xc23f, 0xd61f, 0xd61f, 0xd61f, 0xea3f, 0xea3f, 0xfe5e, 0xfe5e, 0xfe7c, 0xfe97, 0xfe97, 0xfe97, 0xfeb5, 0xff16, 0xff16, 0xfb76, 0xfb76, 0xfb76, - 0xc23f, 0xff9e, 0xc23f, 0xd61f, 0xff3f, 0xea3f, 0xfe5e, 0xff3f, 0xfe7c, 0xfe7c, 0xf2df, 0xfe97, 0xfeb5, 0xfeb5, 0xda7f, 0xff16, 0xc23f, 0xc23f, 0xbe7f, - 0xc23f, 0xff9e, 0xff9e, 0xd61f, 0xff9e, 0xea3f, 0xff3f, 0xfe5e, 0xff3f, 0xfe7a, 0xf2df, 0xf2df, 0xfeb5, 0xf2df, 0xda7f, 0xfb76, 0xc23f, 0xfb76, 0xfb76, - 0xc23f, 0xff9e, 0xd61f, 0xff9e, 0xff9e, 0xfe5e, 0xff3f, 0xff3f, 0xff3f, 0xfe7a, 0xff3f, 0xfeb5, 0xf2df, 0xff16, 0xda7f, 0xfb76, 0xda7f, 0xda7f, 0xfb76, - 0xc23f, 0xff9e, 0xea3f, 0xea3f, 0xff9e, 0xfe5e, 0xff9e, 0xfe7c, 0xff3f, 0xfe97, 0xf2df, 0xfeb5, 0xfeb5, 0xff16, 0xf2df, 0xfb76, 0xda7f, 0xfb76, 0xfb76, - 0xd61f, 0xff9e, 0xd61f, 0xea3f, 0xff9e, 0xfe5e, 0xff9e, 0xfe7c, 0xff3f, 0xfe97, 0xff3f, 0xfeb5, 0xff16, 0xfb76, 0xda7f, 0xfb76, 0xda7f, 0xda7f, 0xc23f, - 0xd61f, 0xd61f, 0xea3f, 0xea3f, 0xea3f, 0xfe5e, 0xfe7c, 0xfe7a, 0xfe7a, 0xfe97, 0xfeb5, 0xff16, 0xff16, 0xfb76, 0xfb76, 0xfb76, 0xfb76, 0xfb76, 0xfb76, - 0xd61f, 0xd61f, 0xea3f, 0xfe5e, 0xfe5e, 0xfe7c, 0xfe7a, 0xfe7a, 0xfe97, 0xfeb5, 0xfeb5, 0xff16, 0xff16, 0xfb76, 0xfb76, 0xfb76, 0xfb76, 0xfb76, 0xfb76, - })}, - - {"30color-16bpp", video::ECF_A1R5G5B5, to_byte_array({ - 0xbabf, 0xbabf, 0xbabf, 0xbabf, 0xbe3f, 0xbdff, 0xc9ff, 0xda1f, 0xee3f, 0xee3f, 0xfa5f, 0xfe5e, 0xfe7c, 0xfe7b, 0xfe79, 0xfe97, 0xfeb5, 0xfed5, 0xff16, - 0xbabf, 0xbabf, 0xff1f, 0xff1f, 0xbdff, 0xfaff, 0xf2df, 0xeabf, 0xee3f, 0xe27f, 0xda7f, 0xfe5e, 0xfe7b, 0xc25f, 0xbe7f, 0xbe7f, 0xfeb5, 0xfed5, 0xff56, - 0xbabf, 0xff7e, 0xbe7f, 0xc25f, 0xbdff, 0xfaff, 0xda1f, 0xf2df, 0xee3f, 0xda7f, 0xfe5e, 0xce5f, 0xfe79, 0xce5f, 0xfe97, 0xfeb5, 0xfed5, 0xff16, 0xff56, - 0xbabf, 0xff7e, 0xbe7f, 0xbdff, 0xc9ff, 0xfaff, 0xda1f, 0xf2df, 0xfa5f, 0xeabf, 0xfe5e, 0xda7f, 0xfe79, 0xce5f, 0xc25f, 0xfeb5, 0xff16, 0xff16, 0xff76, - 0xbabf, 0xff7e, 0xc25f, 0xbdff, 0xc9ff, 0xff1f, 0xda1f, 0xf2df, 0xfa5f, 0xeabf, 0xfe7c, 0xda7f, 0xfe79, 0xce5f, 0xfeb5, 0xfed5, 0xff16, 0xff56, 0xff97, - 0xbabf, 0xbe7f, 0xff7e, 0xff5e, 0xda1f, 0xff1f, 0xfaff, 0xfaff, 0xfa5f, 0xf2df, 0xeabf, 0xfe79, 0xfe97, 0xda7f, 0xce5f, 0xce5f, 0xff56, 0xff76, 0xff97, - 0xbe7f, 0xc25f, 0xbdff, 0xc9ff, 0xda1f, 0xda1f, 0xee3f, 0xfa5f, 0xfa5f, 0xfe7c, 0xfe7b, 0xfe79, 0xfe97, 0xfeb5, 0xfed5, 0xfed5, 0xff76, 0xff76, 0xff97, - 0xbe7f, 0xbe3f, 0xc9ff, 0xc9ff, 0xda1f, 0xee3f, 0xee3f, 0xfa5f, 0xfe5e, 0xfe7c, 0xfe79, 0xfe97, 0xfe97, 0xfeb5, 0xff16, 0xff16, 0xff76, 0xff97, 0xff97, - 0xbe3f, 0xffbe, 0xc9ff, 0xda1f, 0xff5e, 0xee3f, 0xfa5f, 0xff1f, 0xfe7c, 0xfe7b, 0xf2df, 0xfe97, 0xfeb5, 0xfeb5, 0xda7f, 0xff76, 0xce5f, 0xbe3f, 0xc25f, - 0xbe3f, 0xffbe, 0xffbe, 0xda1f, 0xff7e, 0xfa5f, 0xff5e, 0xfe5e, 0xff1f, 0xfe79, 0xf2df, 0xf2df, 0xfeb5, 0xeabf, 0xda7f, 0xff76, 0xce5f, 0xff97, 0xff97, - 0xbdff, 0xffbe, 0xda1f, 0xff7e, 0xff7e, 0xfa5f, 0xff5e, 0xff1f, 0xff1f, 0xfe79, 0xfaff, 0xfeb5, 0xf2df, 0xff16, 0xe27f, 0xff97, 0xda7f, 0xce5f, 0xff97, - 0xbdff, 0xffbe, 0xda1f, 0xee3f, 0xffbe, 0xfa5f, 0xff7e, 0xfe7b, 0xff1f, 0xfe97, 0xfaff, 0xfeb5, 0xfed5, 0xff56, 0xeabf, 0xff97, 0xda7f, 0xff97, 0xff97, - 0xc9ff, 0xffbe, 0xda1f, 0xee3f, 0xff7e, 0xfa5f, 0xff7e, 0xfe7c, 0xff5e, 0xfe97, 0xfaff, 0xfed5, 0xff16, 0xff56, 0xeabf, 0xff97, 0xda7f, 0xda7f, 0xce5f, - 0xda1f, 0xda1f, 0xee3f, 0xee3f, 0xfa5f, 0xfe5e, 0xfe7c, 0xfe79, 0xfe79, 0xfe97, 0xfeb5, 0xfed5, 0xff16, 0xff56, 0xff97, 0xff97, 0xff97, 0xff97, 0xff97, - 0xda1f, 0xda1f, 0xee3f, 0xfa5f, 0xfe5e, 0xfe7c, 0xfe7b, 0xfe79, 0xfe97, 0xfeb5, 0xfed5, 0xff16, 0xff56, 0xff97, 0xff97, 0xff97, 0xff97, 0xff97, 0xff97, - })}, - - {"30color-24bpp", video::ECF_R8G8B8, { - 0x71, 0xaf, 0xff, 0x71, 0xaf, 0xff, 0x71, 0xaf, 0xff, 0x71, 0xaf, 0xff, 0x7b, 0x8b, 0xff, 0x7a, 0x7f, 0xff, 0x90, 0x7f, 0xff, 0xb6, 0x85, 0xff, 0xd9, 0x8c, 0xff, 0xd9, 0x8c, 0xff, 0xf4, 0x92, 0xfe, 0xfe, 0x96, 0xf0, 0xfd, 0x99, 0xe3, 0xfd, 0x9b, 0xda, 0xfc, 0x9f, 0xca, 0xfc, 0xa4, 0xbc, 0xfb, 0xa9, 0xab, 0xfa, 0xb6, 0xad, 0xf9, 0xc6, 0xb1, - 0x71, 0xaf, 0xff, 0x71, 0xaf, 0xff, 0xff, 0xc6, 0xfc, 0xff, 0xc6, 0xfc, 0x7a, 0x7f, 0xff, 0xf7, 0xbe, 0xff, 0xe4, 0xb2, 0xfe, 0xd4, 0xa9, 0xff, 0xd9, 0x8c, 0xff, 0xc3, 0x98, 0xff, 0xb2, 0x9a, 0xff, 0xfe, 0x96, 0xf0, 0xfd, 0x9b, 0xda, 0x86, 0x93, 0xfe, 0x78, 0x9b, 0xff, 0x78, 0x9b, 0xff, 0xfb, 0xa9, 0xab, 0xfa, 0xb6, 0xad, 0xf8, 0xd2, 0xb3, - 0x71, 0xaf, 0xff, 0xfe, 0xdc, 0xf3, 0x78, 0x9b, 0xff, 0x86, 0x93, 0xfe, 0x7a, 0x7f, 0xff, 0xf7, 0xbe, 0xff, 0xb6, 0x85, 0xff, 0xe4, 0xb2, 0xfe, 0xd9, 0x8c, 0xff, 0xb2, 0x9a, 0xff, 0xfe, 0x96, 0xf0, 0x9a, 0x91, 0xff, 0xfc, 0x9f, 0xca, 0x9a, 0x91, 0xff, 0xfc, 0xa4, 0xbc, 0xfb, 0xa9, 0xab, 0xfa, 0xb6, 0xad, 0xf9, 0xc6, 0xb1, 0xf8, 0xd2, 0xb3, - 0x71, 0xaf, 0xff, 0xfe, 0xdc, 0xf3, 0x78, 0x9b, 0xff, 0x7a, 0x7f, 0xff, 0x90, 0x7f, 0xff, 0xf7, 0xbe, 0xff, 0xb6, 0x85, 0xff, 0xe4, 0xb2, 0xfe, 0xf4, 0x92, 0xfe, 0xd4, 0xa9, 0xff, 0xfe, 0x96, 0xf0, 0xb2, 0x9a, 0xff, 0xfc, 0x9f, 0xca, 0x9a, 0x91, 0xff, 0x86, 0x93, 0xfe, 0xfb, 0xa9, 0xab, 0xf9, 0xc6, 0xb1, 0xf9, 0xc6, 0xb1, 0xf8, 0xd8, 0xb5, - 0x71, 0xaf, 0xff, 0xfe, 0xdc, 0xf3, 0x86, 0x93, 0xfe, 0x7a, 0x7f, 0xff, 0x90, 0x7f, 0xff, 0xff, 0xc6, 0xfc, 0xb6, 0x85, 0xff, 0xe4, 0xb2, 0xfe, 0xf4, 0x92, 0xfe, 0xd4, 0xa9, 0xff, 0xfd, 0x99, 0xe3, 0xb2, 0x9a, 0xff, 0xfc, 0x9f, 0xca, 0x9a, 0x91, 0xff, 0xfb, 0xa9, 0xab, 0xfa, 0xb6, 0xad, 0xf9, 0xc6, 0xb1, 0xf8, 0xd2, 0xb3, 0xf8, 0xe4, 0xb9, - 0x71, 0xaf, 0xff, 0x78, 0x9b, 0xff, 0xfe, 0xdc, 0xf3, 0xff, 0xd0, 0xf7, 0xb6, 0x85, 0xff, 0xff, 0xc6, 0xfc, 0xf7, 0xbe, 0xff, 0xf7, 0xbe, 0xff, 0xf4, 0x92, 0xfe, 0xe4, 0xb2, 0xfe, 0xd4, 0xa9, 0xff, 0xfc, 0x9f, 0xca, 0xfc, 0xa4, 0xbc, 0xb2, 0x9a, 0xff, 0x9a, 0x91, 0xff, 0x9a, 0x91, 0xff, 0xf8, 0xd2, 0xb3, 0xf8, 0xd8, 0xb5, 0xf8, 0xe4, 0xb9, - 0x78, 0x9b, 0xff, 0x86, 0x93, 0xfe, 0x7a, 0x7f, 0xff, 0x90, 0x7f, 0xff, 0xb6, 0x85, 0xff, 0xb6, 0x85, 0xff, 0xd9, 0x8c, 0xff, 0xf4, 0x92, 0xfe, 0xf4, 0x92, 0xfe, 0xfd, 0x99, 0xe3, 0xfd, 0x9b, 0xda, 0xfc, 0x9f, 0xca, 0xfc, 0xa4, 0xbc, 0xfb, 0xa9, 0xab, 0xfa, 0xb6, 0xad, 0xfa, 0xb6, 0xad, 0xf8, 0xd8, 0xb5, 0xf8, 0xd8, 0xb5, 0xf8, 0xe4, 0xb9, - 0x78, 0x9b, 0xff, 0x7b, 0x8b, 0xff, 0x90, 0x7f, 0xff, 0x90, 0x7f, 0xff, 0xb6, 0x85, 0xff, 0xd9, 0x8c, 0xff, 0xd9, 0x8c, 0xff, 0xf4, 0x92, 0xfe, 0xfe, 0x96, 0xf0, 0xfd, 0x99, 0xe3, 0xfc, 0x9f, 0xca, 0xfc, 0xa4, 0xbc, 0xfc, 0xa4, 0xbc, 0xfb, 0xa9, 0xab, 0xf9, 0xc6, 0xb1, 0xf9, 0xc6, 0xb1, 0xf8, 0xd8, 0xb5, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, - 0x7b, 0x8b, 0xff, 0xff, 0xeb, 0xf2, 0x90, 0x7f, 0xff, 0xb6, 0x85, 0xff, 0xff, 0xd0, 0xf7, 0xd9, 0x8c, 0xff, 0xf4, 0x92, 0xfe, 0xff, 0xc6, 0xfc, 0xfd, 0x99, 0xe3, 0xfd, 0x9b, 0xda, 0xe4, 0xb2, 0xfe, 0xfc, 0xa4, 0xbc, 0xfb, 0xa9, 0xab, 0xfb, 0xa9, 0xab, 0xb2, 0x9a, 0xff, 0xf8, 0xd8, 0xb5, 0x9a, 0x91, 0xff, 0x7b, 0x8b, 0xff, 0x86, 0x93, 0xfe, - 0x7b, 0x8b, 0xff, 0xff, 0xeb, 0xf2, 0xff, 0xeb, 0xf2, 0xb6, 0x85, 0xff, 0xfe, 0xdc, 0xf3, 0xf4, 0x92, 0xfe, 0xff, 0xd0, 0xf7, 0xfe, 0x96, 0xf0, 0xff, 0xc6, 0xfc, 0xfc, 0x9f, 0xca, 0xe4, 0xb2, 0xfe, 0xe4, 0xb2, 0xfe, 0xfb, 0xa9, 0xab, 0xd4, 0xa9, 0xff, 0xb2, 0x9a, 0xff, 0xf8, 0xd8, 0xb5, 0x9a, 0x91, 0xff, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, - 0x7a, 0x7f, 0xff, 0xff, 0xeb, 0xf2, 0xb6, 0x85, 0xff, 0xfe, 0xdc, 0xf3, 0xfe, 0xdc, 0xf3, 0xf4, 0x92, 0xfe, 0xff, 0xd0, 0xf7, 0xff, 0xc6, 0xfc, 0xff, 0xc6, 0xfc, 0xfc, 0x9f, 0xca, 0xf7, 0xbe, 0xff, 0xfb, 0xa9, 0xab, 0xe4, 0xb2, 0xfe, 0xf9, 0xc6, 0xb1, 0xc3, 0x98, 0xff, 0xf8, 0xe4, 0xb9, 0xb2, 0x9a, 0xff, 0x9a, 0x91, 0xff, 0xf8, 0xe4, 0xb9, - 0x7a, 0x7f, 0xff, 0xff, 0xeb, 0xf2, 0xb6, 0x85, 0xff, 0xd9, 0x8c, 0xff, 0xff, 0xeb, 0xf2, 0xf4, 0x92, 0xfe, 0xfe, 0xdc, 0xf3, 0xfd, 0x9b, 0xda, 0xff, 0xc6, 0xfc, 0xfc, 0xa4, 0xbc, 0xf7, 0xbe, 0xff, 0xfb, 0xa9, 0xab, 0xfa, 0xb6, 0xad, 0xf8, 0xd2, 0xb3, 0xd4, 0xa9, 0xff, 0xf8, 0xe4, 0xb9, 0xb2, 0x9a, 0xff, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, - 0x90, 0x7f, 0xff, 0xff, 0xeb, 0xf2, 0xb6, 0x85, 0xff, 0xd9, 0x8c, 0xff, 0xfe, 0xdc, 0xf3, 0xf4, 0x92, 0xfe, 0xfe, 0xdc, 0xf3, 0xfd, 0x99, 0xe3, 0xff, 0xd0, 0xf7, 0xfc, 0xa4, 0xbc, 0xf7, 0xbe, 0xff, 0xfa, 0xb6, 0xad, 0xf9, 0xc6, 0xb1, 0xf8, 0xd2, 0xb3, 0xd4, 0xa9, 0xff, 0xf8, 0xe4, 0xb9, 0xb2, 0x9a, 0xff, 0xb2, 0x9a, 0xff, 0x9a, 0x91, 0xff, - 0xb6, 0x85, 0xff, 0xb6, 0x85, 0xff, 0xd9, 0x8c, 0xff, 0xd9, 0x8c, 0xff, 0xf4, 0x92, 0xfe, 0xfe, 0x96, 0xf0, 0xfd, 0x99, 0xe3, 0xfc, 0x9f, 0xca, 0xfc, 0x9f, 0xca, 0xfc, 0xa4, 0xbc, 0xfb, 0xa9, 0xab, 0xfa, 0xb6, 0xad, 0xf9, 0xc6, 0xb1, 0xf8, 0xd2, 0xb3, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, - 0xb6, 0x85, 0xff, 0xb6, 0x85, 0xff, 0xd9, 0x8c, 0xff, 0xf4, 0x92, 0xfe, 0xfe, 0x96, 0xf0, 0xfd, 0x99, 0xe3, 0xfd, 0x9b, 0xda, 0xfc, 0x9f, 0xca, 0xfc, 0xa4, 0xbc, 0xfb, 0xa9, 0xab, 0xfa, 0xb6, 0xad, 0xf9, 0xc6, 0xb1, 0xf8, 0xd2, 0xb3, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, 0xf8, 0xe4, 0xb9, - }}, - - {"30color-32bpp", video::ECF_A8R8G8B8, to_byte_array({ - 0xff71afff, 0xff71afff, 0xff71afff, 0xff71afff, 0xff7b8bff, 0xff7a7fff, 0xff907fff, 0xffb685ff, 0xffd98cff, 0xffd98cff, 0xfff492fe, 0xfffe96f0, 0xfffd99e3, 0xfffd9bda, 0xfffc9fca, 0xfffca4bc, 0xfffba9ab, 0xfffab6ad, 0xfff9c6b1, - 0xff71afff, 0xff71afff, 0xffffc6fc, 0xffffc6fc, 0xff7a7fff, 0xfff7beff, 0xffe4b2fe, 0xffd4a9ff, 0xffd98cff, 0xffc398ff, 0xffb29aff, 0xfffe96f0, 0xfffd9bda, 0xff8693fe, 0xff789bff, 0xff789bff, 0xfffba9ab, 0xfffab6ad, 0xfff8d2b3, - 0xff71afff, 0xfffedcf3, 0xff789bff, 0xff8693fe, 0xff7a7fff, 0xfff7beff, 0xffb685ff, 0xffe4b2fe, 0xffd98cff, 0xffb29aff, 0xfffe96f0, 0xff9a91ff, 0xfffc9fca, 0xff9a91ff, 0xfffca4bc, 0xfffba9ab, 0xfffab6ad, 0xfff9c6b1, 0xfff8d2b3, - 0xff71afff, 0xfffedcf3, 0xff789bff, 0xff7a7fff, 0xff907fff, 0xfff7beff, 0xffb685ff, 0xffe4b2fe, 0xfff492fe, 0xffd4a9ff, 0xfffe96f0, 0xffb29aff, 0xfffc9fca, 0xff9a91ff, 0xff8693fe, 0xfffba9ab, 0xfff9c6b1, 0xfff9c6b1, 0xfff8d8b5, - 0xff71afff, 0xfffedcf3, 0xff8693fe, 0xff7a7fff, 0xff907fff, 0xffffc6fc, 0xffb685ff, 0xffe4b2fe, 0xfff492fe, 0xffd4a9ff, 0xfffd99e3, 0xffb29aff, 0xfffc9fca, 0xff9a91ff, 0xfffba9ab, 0xfffab6ad, 0xfff9c6b1, 0xfff8d2b3, 0xfff8e4b9, - 0xff71afff, 0xff789bff, 0xfffedcf3, 0xffffd0f7, 0xffb685ff, 0xffffc6fc, 0xfff7beff, 0xfff7beff, 0xfff492fe, 0xffe4b2fe, 0xffd4a9ff, 0xfffc9fca, 0xfffca4bc, 0xffb29aff, 0xff9a91ff, 0xff9a91ff, 0xfff8d2b3, 0xfff8d8b5, 0xfff8e4b9, - 0xff789bff, 0xff8693fe, 0xff7a7fff, 0xff907fff, 0xffb685ff, 0xffb685ff, 0xffd98cff, 0xfff492fe, 0xfff492fe, 0xfffd99e3, 0xfffd9bda, 0xfffc9fca, 0xfffca4bc, 0xfffba9ab, 0xfffab6ad, 0xfffab6ad, 0xfff8d8b5, 0xfff8d8b5, 0xfff8e4b9, - 0xff789bff, 0xff7b8bff, 0xff907fff, 0xff907fff, 0xffb685ff, 0xffd98cff, 0xffd98cff, 0xfff492fe, 0xfffe96f0, 0xfffd99e3, 0xfffc9fca, 0xfffca4bc, 0xfffca4bc, 0xfffba9ab, 0xfff9c6b1, 0xfff9c6b1, 0xfff8d8b5, 0xfff8e4b9, 0xfff8e4b9, - 0xff7b8bff, 0xffffebf2, 0xff907fff, 0xffb685ff, 0xffffd0f7, 0xffd98cff, 0xfff492fe, 0xffffc6fc, 0xfffd99e3, 0xfffd9bda, 0xffe4b2fe, 0xfffca4bc, 0xfffba9ab, 0xfffba9ab, 0xffb29aff, 0xfff8d8b5, 0xff9a91ff, 0xff7b8bff, 0xff8693fe, - 0xff7b8bff, 0xffffebf2, 0xffffebf2, 0xffb685ff, 0xfffedcf3, 0xfff492fe, 0xffffd0f7, 0xfffe96f0, 0xffffc6fc, 0xfffc9fca, 0xffe4b2fe, 0xffe4b2fe, 0xfffba9ab, 0xffd4a9ff, 0xffb29aff, 0xfff8d8b5, 0xff9a91ff, 0xfff8e4b9, 0xfff8e4b9, - 0xff7a7fff, 0xffffebf2, 0xffb685ff, 0xfffedcf3, 0xfffedcf3, 0xfff492fe, 0xffffd0f7, 0xffffc6fc, 0xffffc6fc, 0xfffc9fca, 0xfff7beff, 0xfffba9ab, 0xffe4b2fe, 0xfff9c6b1, 0xffc398ff, 0xfff8e4b9, 0xffb29aff, 0xff9a91ff, 0xfff8e4b9, - 0xff7a7fff, 0xffffebf2, 0xffb685ff, 0xffd98cff, 0xffffebf2, 0xfff492fe, 0xfffedcf3, 0xfffd9bda, 0xffffc6fc, 0xfffca4bc, 0xfff7beff, 0xfffba9ab, 0xfffab6ad, 0xfff8d2b3, 0xffd4a9ff, 0xfff8e4b9, 0xffb29aff, 0xfff8e4b9, 0xfff8e4b9, - 0xff907fff, 0xffffebf2, 0xffb685ff, 0xffd98cff, 0xfffedcf3, 0xfff492fe, 0xfffedcf3, 0xfffd99e3, 0xffffd0f7, 0xfffca4bc, 0xfff7beff, 0xfffab6ad, 0xfff9c6b1, 0xfff8d2b3, 0xffd4a9ff, 0xfff8e4b9, 0xffb29aff, 0xffb29aff, 0xff9a91ff, - 0xffb685ff, 0xffb685ff, 0xffd98cff, 0xffd98cff, 0xfff492fe, 0xfffe96f0, 0xfffd99e3, 0xfffc9fca, 0xfffc9fca, 0xfffca4bc, 0xfffba9ab, 0xfffab6ad, 0xfff9c6b1, 0xfff8d2b3, 0xfff8e4b9, 0xfff8e4b9, 0xfff8e4b9, 0xfff8e4b9, 0xfff8e4b9, - 0xffb685ff, 0xffb685ff, 0xffd98cff, 0xfff492fe, 0xfffe96f0, 0xfffd99e3, 0xfffd9bda, 0xfffc9fca, 0xfffca4bc, 0xfffba9ab, 0xfffab6ad, 0xfff9c6b1, 0xfff8d2b3, 0xfff8e4b9, 0xfff8e4b9, 0xfff8e4b9, 0xfff8e4b9, 0xfff8e4b9, 0xfff8e4b9, - })}, - -}; - -void printImageBytes(const video::IImage *img) -{ - const auto *data = (u8 *)img->getData(); - const auto w = img->getPitch(); - const auto h = img->getDimension().Height; - for (int y = 0; y < h; y++) { - for (int k = 0; k < w; k++) { - std::printf("0x%02x, ", *data++); - } - std::printf("\n"); - } -} - -int main(int argc, char *argv[]) -try { - if (argc != 3) - throw std::runtime_error("Invalid arguments. Expected sample ID and image file name"); - - const ImageDesc *sample = nullptr; - for (auto &&image : test_images) { - if (strcmp(argv[1], image.name) == 0) - sample = ℑ - } - if (!sample) - throw std::runtime_error("Sample not found"); - - SIrrlichtCreationParameters p; - p.DriverType = video::EDT_NULL; - p.WindowSize = core::dimension2du(640, 480); - p.LoggingLevel = ELL_DEBUG; - - auto *device = createDeviceEx(p); - if (!device) - throw std::runtime_error("Failed to create device"); - - auto *driver = device->getVideoDriver(); - - auto *img = driver->createImageFromFile(argv[2]); - if (!img) - throw std::runtime_error("Failed to load image"); - - if (img->getDimension() != core::dimension2du{19, 15}) - throw std::runtime_error("Wrong image dimensions"); - - if (img->getColorFormat() != sample->format) - throw std::runtime_error("Wrong image format"); - - if (img->getImageDataSizeInBytes() != sample->data.size()) - throw std::logic_error("Image data size not equal to sample size"); - - if (memcmp(img->getData(), sample->data.data(), sample->data.size()) != 0) { - printImageBytes(img); - throw std::runtime_error("Wrong image contents"); - } - - img->drop(); - device->drop(); - - return 0; -} catch (const std::exception &e) { - std::printf("Test failed: %s\n", e.what()); - return 1; -} From ec115ffe2ae89ef633d3a7e5a9f01b4e59b62c47 Mon Sep 17 00:00:00 2001 From: Daniel Hajjar <33489389+DanTGL@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:49:15 +0200 Subject: [PATCH 011/200] Make SecureRandom non-failable --- doc/lua_api.md | 2 +- src/script/lua_api/l_noise.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 4d461f335..27a9fef96 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8669,7 +8669,7 @@ In multiplayer mode, the error may be arbitrarily large. Interface for the operating system's crypto-secure PRNG. -It can be created via `SecureRandom()`. The constructor returns nil if a +It can be created via `SecureRandom()`. The constructor throws an error if a secure random device cannot be found on the system. ### Methods diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index 9836f4b09..ead14fec0 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -641,10 +641,9 @@ int LuaSecureRandom::create_object(lua_State *L) { LuaSecureRandom *o = new LuaSecureRandom(); - // Fail and return nil if we can't securely fill the buffer if (!o->fillRandBuf()) { delete o; - return 0; + throw LuaError("SecureRandom: Failed to find secure random device on system"); } *(void **)(lua_newuserdata(L, sizeof(void *))) = o; From 9ccd9d341ffa6fc5c73c42501e92181840cbb930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:49:41 +0200 Subject: [PATCH 012/200] Revert empty form name deprecation warnings --- doc/lua_api.md | 3 ++- src/script/lua_api/l_server.cpp | 12 +----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 27a9fef96..dde797325 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6463,7 +6463,8 @@ Formspec * `playername`: name of player to show formspec * `formname`: name passed to `on_player_receive_fields` callbacks. It should follow the `"modname:"` naming convention. - `formname` must not be empty. + * `formname` must not be empty, unless you want to reshow + the inventory formspec without updating it for future opens. * `formspec`: formspec to display * `minetest.close_formspec(playername, formname)` * `playername`: name of player to close formspec diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index af9a526e0..69f5cf9a0 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -426,18 +426,8 @@ int ModApiServer::l_show_formspec(lua_State *L) NO_MAP_LOCK_REQUIRED; const char *playername = luaL_checkstring(L, 1); const char *formname = luaL_checkstring(L, 2); - if (*formname == '\0') { - log_deprecated(L, "Deprecated call to `minetest.show_formspec`:" - "`formname` must not be empty"); - } const char *formspec = luaL_checkstring(L, 3); - - if(getServer(L)->showFormspec(playername,formspec,formname)) - { - lua_pushboolean(L, true); - }else{ - lua_pushboolean(L, false); - } + lua_pushboolean(L, getServer(L)->showFormspec(playername,formspec,formname)); return 1; } From 2664afd83230e3f1509f0d66cba0b8c18808a9ad Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Tue, 20 Aug 2024 10:50:29 +0100 Subject: [PATCH 013/200] Fix Windows enabling touch controls due to existence of touchscreen (#15003) We want to check for the form factor instead. --- src/defaultsettings.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index f8cfd18b4..7d3273271 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -76,13 +76,6 @@ static bool detect_touch() return false; } - return false; -#elif defined(_WIN32) - // 0x01 The device has an integrated touch digitizer - // 0x80 The device is ready to receive digitizer input. - if ((GetSystemMetrics(SM_DIGITIZER) & 0x81) == 0x81) - return true; - return false; #else // we don't know, return default From dc21924f31ff52174553c4441ce8c861f842c6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:51:52 +0200 Subject: [PATCH 014/200] Fix animations not being restartable (#15016) --- src/client/content_cao.cpp | 5 ++--- src/server/unit_sao.cpp | 5 +---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 7913e477a..19bee6f7f 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1518,9 +1518,8 @@ void GenericCAO::updateAnimation() if (!m_animated_meshnode) return; - if (m_animated_meshnode->getStartFrame() != m_animation_range.X || - m_animated_meshnode->getEndFrame() != m_animation_range.Y) - m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y); + // Note: This sets the current frame as well, (re)starting the animation. + m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y); if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed) m_animated_meshnode->setAnimationSpeed(m_animation_speed); m_animated_meshnode->setTransitionTime(m_animation_blend); diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index d764b5d16..a4f547146 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -55,10 +55,7 @@ const ItemGroupList &UnitSAO::getArmorGroups() const void UnitSAO::setAnimation( v2f frame_range, float frame_speed, float frame_blend, bool frame_loop) { - if (std::tie(m_animation_range, m_animation_speed, m_animation_blend, - m_animation_loop) == - std::tie(frame_range, frame_speed, frame_blend, frame_loop)) - return; // no change + // Note: Always resend (even if parameters are unchanged) to restart animations. m_animation_range = frame_range; m_animation_speed = frame_speed; m_animation_blend = frame_blend; From f2c66b9ceb4930dfd7b510c77037c235b161e603 Mon Sep 17 00:00:00 2001 From: sfence Date: Wed, 21 Aug 2024 20:24:43 +0200 Subject: [PATCH 015/200] Add possibility to easier override HP and breath engine logic by Lua (#14179) Co-authored-by: Lars Mueller --- doc/lua_api.md | 8 +++++++ src/script/common/c_content.cpp | 2 +- src/script/lua_api/l_object.cpp | 37 +++++++++++++++++++++++++++++++++ src/script/lua_api/l_object.h | 6 ++++++ src/server/player_sao.cpp | 11 +++++++--- src/server/player_sao.h | 6 ++++++ 6 files changed, 66 insertions(+), 4 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index dde797325..07c7b3c2e 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8495,6 +8495,14 @@ child will follow movement and rotation of that bone. * Result is a table with the same fields as `light_definition` in `set_lighting`. * `respawn()`: Respawns the player using the same mechanism as the death screen, including calling `on_respawnplayer` callbacks. +* `get_flags()`: returns a table of player flags (the following boolean fields): + * `breathing`: Whether breathing (regaining air) is enabled, default `true`. + * `drowning`: Whether drowning (losing air) is enabled, default `true`. + * `node_damage`: Whether the player takes damage from nodes, default `true`. +* `set_flags(flags)`: sets flags + * takes a table in the same format as returned by `get_flags` + * absent fields are left unchanged + `PcgRandom` ----------- diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 6f7d86447..bd76a3ad3 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -291,7 +291,7 @@ const std::array object_property_keys = { "use_texture_alpha", "shaded", "damage_texture_modifier", - "show_on_minimap" + "show_on_minimap", }; /******************************************************************************/ diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 00c825ddc..eb0a375d4 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2688,6 +2688,41 @@ int ObjectRef::l_respawn(lua_State *L) return 1; } +// set_flags(self, flags) +int ObjectRef::l_set_flags(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkObject(L, 1); + auto *psao = getplayersao(ref); + if (psao == nullptr) + return 0; + if (!lua_istable(L, -1)) + throw LuaError("expected a table of flags"); + auto &flags = psao->m_flags; + flags.drowning = getboolfield_default(L, -1, "drowning", flags.drowning); + flags.breathing = getboolfield_default(L, -1, "breathing", flags.breathing); + flags.node_damage = getboolfield_default(L, -1, "node_damage", flags.node_damage); + return 0; +} + +// get_flags(self) +int ObjectRef::l_get_flags(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkObject(L, 1); + const auto *psao = getplayersao(ref); + if (psao == nullptr) + return 0; + lua_createtable(L, 0, 3); + lua_pushboolean(L, psao->m_flags.drowning); + lua_setfield(L, -2, "drowning"); + lua_pushboolean(L, psao->m_flags.breathing); + lua_setfield(L, -2, "breathing"); + lua_pushboolean(L, psao->m_flags.node_damage); + lua_setfield(L, -2, "node_damage"); + return 1; +} + ObjectRef::ObjectRef(ServerActiveObject *object): m_object(object) @@ -2838,6 +2873,8 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, set_lighting), luamethod(ObjectRef, get_lighting), luamethod(ObjectRef, respawn), + luamethod(ObjectRef, set_flags), + luamethod(ObjectRef, get_flags), {0,0} }; diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index ace19e1f0..75e961438 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -411,4 +411,10 @@ private: // respawn(self) static int l_respawn(lua_State *L); + + // set_flags(self, flags) + static int l_set_flags(lua_State *L); + + // get_flags(self) + static int l_get_flags(lua_State *L); }; diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 4abb1f920..11922b2c6 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -156,7 +156,10 @@ void PlayerSAO::getStaticData(std::string * result) const void PlayerSAO::step(float dtime, bool send_recommended) { - if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) { + bool not_immortal = !isImmortal(); + + if (not_immortal && m_flags.drowning + && m_drowning_interval.step(dtime, 2.0f)) { // Get nose/mouth position, approximate with eye position v3s16 p = floatToInt(getEyePosition(), BS); MapNode n = m_env->getMap().getNode(p); @@ -174,7 +177,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) } } - if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) { + if (not_immortal && m_flags.breathing + && m_breathing_interval.step(dtime, 0.5f)) { // Get nose/mouth position, approximate with eye position v3s16 p = floatToInt(getEyePosition(), BS); MapNode n = m_env->getMap().getNode(p); @@ -185,7 +189,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) setBreath(m_breath + 1); } - if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) { + if (not_immortal && m_flags.node_damage + && m_node_hurt_interval.step(dtime, 1.0f)) { u32 damage_per_second = 0; std::string nodename; v3s16 node_pos; diff --git a/src/server/player_sao.h b/src/server/player_sao.h index b26304589..95bd1d109 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -228,6 +228,12 @@ private: SimpleMetadata m_meta; public: + struct { + bool breathing : 1; + bool drowning : 1; + bool node_damage : 1; + } m_flags = {true, true, true}; + bool m_physics_override_sent = false; }; From 1bccb4e48ce4db8851d3d6eb4a574e7f56a9f8fd Mon Sep 17 00:00:00 2001 From: wrrrzr <161970349+wrrrzr@users.noreply.github.com> Date: Wed, 21 Aug 2024 21:24:59 +0300 Subject: [PATCH 016/200] Refactor tool.cpp (#14873) Co-authored-by: sfan5 --- src/tool.cpp | 107 ++++++++++++++++++++++++++++++--------------------- src/tool.h | 4 ++ 2 files changed, 68 insertions(+), 43 deletions(-) diff --git a/src/tool.cpp b/src/tool.cpp index 9df69eccd..0cb1724de 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -44,18 +44,25 @@ void ToolGroupCap::toJson(Json::Value &object) const void ToolGroupCap::fromJson(const Json::Value &json) { - if (json.isObject()) { - if (json["maxlevel"].isInt()) - maxlevel = json["maxlevel"].asInt(); - if (json["uses"].isInt()) - uses = json["uses"].asInt(); - const Json::Value ×_object = json["times"]; - if (times_object.isArray()) { - Json::ArrayIndex size = times_object.size(); - for (Json::ArrayIndex i = 0; i < size; ++i) - if (times_object[i].isDouble()) - times[i] = times_object[i].asFloat(); - } + if (!json.isObject()) + return; + + if (json["maxlevel"].isInt()) + maxlevel = json["maxlevel"].asInt(); + + if (json["uses"].isInt()) + uses = json["uses"].asInt(); + + const Json::Value ×_object = json["times"]; + + if (!times_object.isArray()) + return; + + Json::ArrayIndex size = times_object.size(); + + for (Json::ArrayIndex i = 0; i < size; ++i) { + if (times_object[i].isDouble()) + times[i] = times_object[i].asFloat(); } } @@ -65,9 +72,11 @@ void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, 5); else writeU8(os, 4); // proto == 37 + writeF32(os, full_punch_interval); writeS16(os, max_drop_level); writeU32(os, groupcaps.size()); + for (const auto &groupcap : groupcaps) { const std::string *name = &groupcap.first; const ToolGroupCap *cap = &groupcap.second; @@ -102,6 +111,7 @@ void ToolCapabilities::deSerialize(std::istream &is) max_drop_level = readS16(is); groupcaps.clear(); u32 groupcaps_size = readU32(is); + for (u32 i = 0; i < groupcaps_size; i++) { std::string name = deSerializeString16(is); ToolGroupCap cap; @@ -135,15 +145,19 @@ void ToolCapabilities::serializeJson(std::ostream &os) const root["punch_attack_uses"] = punch_attack_uses; Json::Value groupcaps_object; + for (const auto &groupcap : groupcaps) { groupcap.second.toJson(groupcaps_object[groupcap.first]); } + root["groupcaps"] = std::move(groupcaps_object); Json::Value damage_groups_object; + for (const auto &damagegroup : damageGroups) { damage_groups_object[damagegroup.first] = damagegroup.second; } + root["damage_groups"] = std::move(damage_groups_object); fastWriteJson(root, os); @@ -153,36 +167,44 @@ void ToolCapabilities::deserializeJson(std::istream &is) { Json::Value root; is >> root; - if (root.isObject()) { - if (root["full_punch_interval"].isDouble()) - full_punch_interval = root["full_punch_interval"].asFloat(); - if (root["max_drop_level"].isInt()) - max_drop_level = root["max_drop_level"].asInt(); - if (root["punch_attack_uses"].isInt()) - punch_attack_uses = root["punch_attack_uses"].asInt(); - Json::Value &groupcaps_object = root["groupcaps"]; - if (groupcaps_object.isObject()) { - Json::ValueIterator gciter; - for (gciter = groupcaps_object.begin(); - gciter != groupcaps_object.end(); ++gciter) { - ToolGroupCap groupcap; - groupcap.fromJson(*gciter); - groupcaps[gciter.key().asString()] = groupcap; - } - } + if (!root.isObject()) + return; - Json::Value &damage_groups_object = root["damage_groups"]; - if (damage_groups_object.isObject()) { - Json::ValueIterator dgiter; - for (dgiter = damage_groups_object.begin(); - dgiter != damage_groups_object.end(); ++dgiter) { - Json::Value &value = *dgiter; - if (value.isInt()) - damageGroups[dgiter.key().asString()] = - value.asInt(); - } - } + if (root["full_punch_interval"].isDouble()) + full_punch_interval = root["full_punch_interval"].asFloat(); + + if (root["max_drop_level"].isInt()) + max_drop_level = root["max_drop_level"].asInt(); + + if (root["punch_attack_uses"].isInt()) + punch_attack_uses = root["punch_attack_uses"].asInt(); + + deserializeJsonGroupcaps(root["groupcaps"]); + deserializeJsonDamageGroups(root["damage_groups"]); +} + +void ToolCapabilities::deserializeJsonGroupcaps(Json::Value &json) +{ + if (!json.isObject()) + return; + + for (Json::ValueIterator iter = json.begin(); iter != json.end(); ++iter) { + ToolGroupCap value; + value.fromJson(*iter); + groupcaps[iter.key().asString()] = value; + } +} + +void ToolCapabilities::deserializeJsonDamageGroups(Json::Value &json) +{ + if (!json.isObject()) + return; + + for (Json::ValueIterator iter = json.begin(); iter != json.end(); ++iter) { + Json::Value &value = *iter; + if (value.isInt()) + damageGroups[iter.key().asString()] = value.asInt(); } } @@ -362,9 +384,8 @@ u32 calculateResultWear(const u32 uses, const u16 initial_wear) player. */ u16 wear_extra_at = blocks_normal * wear_normal; - if (initial_wear >= wear_extra_at) { + if (initial_wear >= wear_extra_at) wear_extra = 1; - } } result_wear = wear_normal + wear_extra; return result_wear; @@ -410,6 +431,7 @@ DigParams getDigParams(const ItemGroupList &groups, if (leveldiff > 1) time /= leveldiff; + if (!result_diggable || time < result_time) { result_time = time; result_diggable = true; @@ -509,4 +531,3 @@ f32 getToolRange(const ItemStack &wielded_item, const ItemStack &hand_item, return max_d; } - diff --git a/src/tool.h b/src/tool.h index 4b25d3a62..c3d811dd4 100644 --- a/src/tool.h +++ b/src/tool.h @@ -83,6 +83,10 @@ struct ToolCapabilities void deSerialize(std::istream &is); void serializeJson(std::ostream &os) const; void deserializeJson(std::istream &is); + +private: + void deserializeJsonGroupcaps(Json::Value &json); + void deserializeJsonDamageGroups(Json::Value &json); }; struct WearBarParams From 6cc0452503fad764a9f64d30540b37737879eeea Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Wed, 21 Aug 2024 19:25:18 +0100 Subject: [PATCH 017/200] Generate Android versionCode from Major.Minor.Patch (#14963) --- android/app/build.gradle | 14 +------------- android/build.gradle | 9 ++++----- android/native/build.gradle | 2 +- util/bump_version.sh | 29 +---------------------------- 4 files changed, 7 insertions(+), 47 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index fe6c4ab0d..cefc473af 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -8,7 +8,7 @@ android { compileSdk 34 targetSdkVersion 34 versionName "${versionMajor}.${versionMinor}.${versionPatch}" - versionCode project.versionCode + versionCode versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild } buildFeatures { @@ -116,18 +116,6 @@ clean { delete new File("src/main/assets", "Minetest.zip") } -// Map for the version code that gives each ABI a value. -import com.android.build.OutputFile - -def abiCodes = ['armeabi-v7a': 0, 'arm64-v8a': 1] -android.applicationVariants.all { variant -> - variant.outputs.each { - output -> - def abiName = output.getFilter(OutputFile.ABI) - output.versionCodeOverride = abiCodes.get(abiName, 0) + variant.versionCode - } -} - dependencies { implementation project(':native') implementation 'androidx.appcompat:appcompat:1.6.1' diff --git a/android/build.gradle b/android/build.gradle index 69fd26625..2017c536c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,13 +1,12 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. project.ext.set("versionMajor", 5) // Version Major -project.ext.set("versionMinor", 10) // Version Minor +project.ext.set("versionMinor", 10) // Version Minor project.ext.set("versionPatch", 0) // Version Patch // ^ keep in sync with cmake -project.ext.set("versionCode", 48) // Android Version Code -// NOTE: +2 after each release! -// +1 for ARM and +1 for ARM64 APK's, because -// each APK must have a larger `versionCode` than the previous + +project.ext.set("versionBuild", 0) // Version Build +// ^ fourth version number to allow releasing Android-only fixes and beta versions buildscript { ext.ndk_version = '26.2.11394342' diff --git a/android/native/build.gradle b/android/native/build.gradle index 0be7c1d99..806dda2f0 100644 --- a/android/native/build.gradle +++ b/android/native/build.gradle @@ -8,7 +8,7 @@ android { compileSdk 34 targetSdkVersion 34 externalNativeBuild { - cmake { + cmake { arguments "-DANDROID_STL=c++_shared", "-DENABLE_CURL=1", "-DENABLE_SOUND=1", "-DENABLE_GETTEXT=1", diff --git a/util/bump_version.sh b/util/bump_version.sh index 45452c295..699bbcf77 100755 --- a/util/bump_version.sh +++ b/util/bump_version.sh @@ -16,20 +16,18 @@ prompt_for() { } # Reads current versions -# out: VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_IS_DEV CURRENT_VERSION ANDROID_VERSION_CODE +# out: VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_IS_DEV CURRENT_VERSION read_versions() { VERSION_MAJOR=$(grep -oE '^set\(VERSION_MAJOR [0-9]+\)$' CMakeLists.txt | tr -dC 0-9) VERSION_MINOR=$(grep -oE '^set\(VERSION_MINOR [0-9]+\)$' CMakeLists.txt | tr -dC 0-9) VERSION_PATCH=$(grep -oE '^set\(VERSION_PATCH [0-9]+\)$' CMakeLists.txt | tr -dC 0-9) VERSION_IS_DEV=$(grep -oE '^set\(DEVELOPMENT_BUILD [A-Z]+\)$' CMakeLists.txt) - ANDROID_VERSION_CODE=$(grep -oE '\("versionCode", [0-9]+\)' android/build.gradle | tr -dC 0-9) # Make sure they all exist [ -n "$VERSION_MAJOR" ] [ -n "$VERSION_MINOR" ] [ -n "$VERSION_PATCH" ] [ -n "$VERSION_IS_DEV" ] - [ -n "$ANDROID_VERSION_CODE" ] if echo "$VERSION_IS_DEV" | grep -q ' TRUE'; then VERSION_IS_DEV=1 @@ -39,7 +37,6 @@ read_versions() { CURRENT_VERSION="$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" echo "Current Minetest version: $CURRENT_VERSION" - echo "Current Android version code: $ANDROID_VERSION_CODE" } # Retrieves protocol version from header @@ -49,18 +46,6 @@ read_proto_ver() { git show "$ref":src/network/networkprotocol.h | grep -oE 'LATEST_PROTOCOL_VERSION [0-9]+' | tr -dC 0-9 } -## Prompts for new android version code -# in: ANDROID_VERSION_CODE -# out: NEW_ANDROID_VERSION_CODE -bump_android_ver() { - # +1 for ARM and +1 for ARM64 APKs - NEW_ANDROID_VERSION_CODE=$(expr $ANDROID_VERSION_CODE + 2) - NEW_ANDROID_VERSION_CODE=$(prompt_for "Set android version code" '[0-9]+' $NEW_ANDROID_VERSION_CODE) - - echo - echo "New android version code: $NEW_ANDROID_VERSION_CODE" -} - ## Prompts for new version # in: VERSION_{MAJOR,MINOR,PATCH} DO_PATCH_REL # out: NEXT_VERSION NEXT_VERSION_{MAJOR,MINOR,PATCH} @@ -108,14 +93,6 @@ set_dev_build() { git add -f CMakeLists.txt android/build.gradle } -## Writes new android version code -# in: NEW_ANDROID_VERSION_CODE -write_android_version() { - sed -i -re "s/\"versionCode\", [0-9]+/\"versionCode\", $NEW_ANDROID_VERSION_CODE/" android/build.gradle - - git add -f android/build.gradle -} - ## Writes new version to the right files # in: NEXT_VERSION NEXT_VERSION_{MAJOR,MINOR,PATCH} write_new_version() { @@ -198,8 +175,6 @@ if [ "$DO_PATCH_REL" -eq 0 ]; then exit 1 fi - bump_android_ver - write_android_version set_dev_build 0 perform_release "$CURRENT_VERSION" @@ -212,8 +187,6 @@ if [ "$DO_PATCH_REL" -eq 0 ]; then else # On a patch release the version moves from 5.7.0 -> 5.7.1 (new tag) - bump_android_ver - write_android_version bump_version write_new_version From b2f6a65bc9d8b9af394c15db277db4cb6d2e8a55 Mon Sep 17 00:00:00 2001 From: Zemtzov7 <72821250+zmv7@users.noreply.github.com> Date: Wed, 21 Aug 2024 23:25:41 +0500 Subject: [PATCH 018/200] Sort clients in `minetest.get_server_status` and privs in `minetest.privs_to_string` (#15023) --- builtin/common/misc_helpers.lua | 1 + builtin/game/chat.lua | 1 + src/server.cpp | 9 +++------ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index fb38c1b35..21752ce7f 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -702,6 +702,7 @@ function core.privs_to_string(privs, delim) list[#list + 1] = priv end end + table.sort(list) return table.concat(list, delim) end diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index baab5212f..b7e2aea50 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -221,6 +221,7 @@ core.register_chatcommand("haspriv", { return true, S("No online player has the \"@1\" privilege.", param) else + table.sort(players_with_priv) return true, S("Players online with the \"@1\" privilege: @2", param, table.concat(players_with_priv, ", ")) diff --git a/src/server.cpp b/src/server.cpp index 0b0786209..1d98e4634 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -3181,14 +3181,11 @@ std::string Server::getStatusString() bool first = true; os << " | clients: "; if (m_env) { - std::vector clients = m_clients.getClientIDs(); - for (session_t client_id : clients) { - RemotePlayer *player = m_env->getPlayer(client_id); + std::vector player_names = m_clients.getPlayerNames(); - // Get name of player - const std::string name = player ? player->getName() : ""; + std::sort(player_names.begin(), player_names.end()); - // Add name to information string + for (const std::string& name : player_names) { if (!first) os << ", "; else From 66b3db360115de5d46cc036b2387810bf1460bd5 Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 21 Aug 2024 20:25:58 +0200 Subject: [PATCH 019/200] Fix mods folder being read twice with RUN_IN_PLACE=1 (#15024) --- doc/menu_lua_api.md | 2 +- src/script/lua_api/l_mainmenu.cpp | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index 9f0e23a11..8f945052c 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -282,7 +282,7 @@ Package - content which is downloadable from the content db, may or may not be i ```lua { mods = "/home/user/.minetest/mods", - share = "/usr/share/minetest/mods", + share = "/usr/share/minetest/mods", -- only provided when RUN_IN_PLACE=0 -- Custom dirs can be specified by the MINETEST_MOD_DIR env variable ["/path/to/custom/dir"] = "/path/to/custom/dir", diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 97101955a..78808792b 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -675,10 +675,12 @@ int ModApiMainMenu::l_get_modpaths(lua_State *L) ModApiMainMenu::l_get_modpath(L); lua_setfield(L, -2, "mods"); - std::string modpath = fs::RemoveRelativePathComponents( - porting::path_share + DIR_DELIM + "mods" + DIR_DELIM); - lua_pushstring(L, modpath.c_str()); - lua_setfield(L, -2, "share"); + if (porting::path_share != porting::path_user) { + std::string modpath = fs::RemoveRelativePathComponents( + porting::path_share + DIR_DELIM + "mods" + DIR_DELIM); + lua_pushstring(L, modpath.c_str()); + lua_setfield(L, -2, "share"); + } for (const std::string &component : getEnvModPaths()) { lua_pushstring(L, component.c_str()); From ab7af5d15a8250c24628b283940401ccc3759c30 Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Wed, 21 Aug 2024 20:30:58 +0200 Subject: [PATCH 020/200] Fix trailing whitespace from #14179 --- src/script/lua_api/l_object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 75e961438..8225aa470 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -414,7 +414,7 @@ private: // set_flags(self, flags) static int l_set_flags(lua_State *L); - + // get_flags(self) static int l_get_flags(lua_State *L); }; From c6ef5ab2595d55192e7952694c6b301f2b64f65c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 21 Aug 2024 21:34:46 +0200 Subject: [PATCH 021/200] Sanitize formspec fields server-side (#14878) --- doc/lua_api.md | 3 ++ src/network/serverpackethandler.cpp | 17 ++++++++--- src/unittest/test_utilities.cpp | 27 +++++++++++++++++ src/util/string.cpp | 47 +++++++++++++++++++++++++++++ src/util/string.h | 10 ++++++ 5 files changed, 99 insertions(+), 5 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 07c7b3c2e..a43f517a0 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -2625,6 +2625,9 @@ background elements are drawn before all other elements. **WARNING**: do _not_ use an element name starting with `key_`; those names are reserved to pass key press events to formspec! +**WARNING**: names and values of elements cannot contain binary data such as ASCII +control characters. For values, escape sequences used by the engine are an exception to this. + **WARNING**: Minetest allows you to add elements to every single formspec instance using `player:set_formspec_prepend()`, which may be the reason backgrounds are appearing when you don't expect them to, or why things are styled differently diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 9de39229b..c0718d43b 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -1351,15 +1351,22 @@ static bool pkt_read_formspec_fields(NetworkPacket *pkt, StringMap &fields) u16 field_count; *pkt >> field_count; - u64 length = 0; + size_t length = 0; for (u16 k = 0; k < field_count; k++) { - std::string fieldname; + std::string fieldname, fieldvalue; *pkt >> fieldname; - fields[fieldname] = pkt->readLongString(); + fieldvalue = pkt->readLongString(); - length += fieldname.size(); - length += fields[fieldname].size(); + fieldname = sanitize_untrusted(fieldname, false); + // We'd love to strip escapes here but some formspec elements reflect data + // from the server (e.g. dropdown), which can contain translations. + fieldvalue = sanitize_untrusted(fieldvalue); + + length += fieldname.size() + fieldvalue.size(); + + fields[std::move(fieldname)] = std::move(fieldvalue); } + // 640K ought to be enough for anyone return length < 640 * 1024; } diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 996b418e3..a05421c9b 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -61,6 +61,7 @@ public: void testSanitizeDirName(); void testIsBlockInSight(); void testColorizeURL(); + void testSanitizeUntrusted(); }; static TestUtilities g_test_instance; @@ -95,6 +96,7 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testSanitizeDirName); TEST(testIsBlockInSight); TEST(testColorizeURL); + TEST(testSanitizeUntrusted); } //////////////////////////////////////////////////////////////////////////////// @@ -743,3 +745,28 @@ void TestUtilities::testColorizeURL() warningstream << "Test skipped." << std::endl; #endif } + +void TestUtilities::testSanitizeUntrusted() +{ + std::string_view t1{u8"Anästhesieausrüstung"}; + UASSERTEQ(auto, sanitize_untrusted(t1), t1); + + std::string_view t2{"stop\x00here", 9}; + UASSERTEQ(auto, sanitize_untrusted(t2), "stop"); + + UASSERTEQ(auto, sanitize_untrusted("\x01\x08\x13\x1dhello\r\n\tworld"), "hello\n\tworld"); + + std::string_view t3{"some \x1b(T@whatever)text\x1b" "E here"}; + UASSERTEQ(auto, sanitize_untrusted(t3), t3); + auto t3_sanitized = sanitize_untrusted(t3, false); + UASSERT(str_starts_with(t3_sanitized, "some ") && str_ends_with(t3_sanitized, " here")); + UASSERT(t3_sanitized.find('\x1b') == std::string::npos); + + UASSERTEQ(auto, sanitize_untrusted("\x1b[31m"), "[31m"); + + // edge cases + for (bool keep : {true, false}) { + UASSERTEQ(auto, sanitize_untrusted("\x1b", keep), ""); + UASSERTEQ(auto, sanitize_untrusted("\x1b(", keep), "("); + } +} diff --git a/src/util/string.cpp b/src/util/string.cpp index 0c896e6ec..73d1d6907 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -950,6 +950,53 @@ std::string sanitizeDirName(std::string_view str, std::string_view optional_pref return wide_to_utf8(safe_name); } +template +void remove_indexed(std::string &s, F pred) +{ + size_t j = 0; + for (size_t i = 0; i < s.length();) { + if (pred(s, i++)) + j++; + if (i != j) + s[j] = s[i]; + } + s.resize(j); +} + +std::string sanitize_untrusted(std::string_view str, bool keep_escapes) +{ + // truncate on NULL + std::string s{str.substr(0, str.find('\0'))}; + + // remove control characters except tab, feed and escape + s.erase(std::remove_if(s.begin(), s.end(), [] (unsigned char c) { + return c < 9 || (c >= 13 && c < 27) || (c >= 28 && c < 32); + }), s.end()); + + if (!keep_escapes) { + s.erase(std::remove(s.begin(), s.end(), '\x1b'), s.end()); + return s; + } + // Note: Minetest escapes generally just look like \x1b# or \x1b(###) + // where # is a single character and ### any number of characters. + // Here we additionally assume that the first character in the sequence + // is [A-Za-z], to enable us to filter foreign types of escapes that might + // be unsafe e.g. ANSI escapes in a terminal. + const auto &check = [] (char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + }; + remove_indexed(s, [&check] (const std::string &s, size_t i) { + if (s[i] != '\x1b') + return true; + if (i+1 >= s.length()) + return false; + if (s[i+1] == '(') + return i+2 < s.length() && check(s[i+2]); // long-form escape + else + return check(s[i+1]); // short-form escape + }); + return s; +} void safe_print_string(std::ostream &os, std::string_view str) { diff --git a/src/util/string.h b/src/util/string.h index bddbc62ce..ad3d09818 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -761,6 +761,16 @@ inline irr::core::stringw utf8_to_stringw(std::string_view input) */ std::string sanitizeDirName(std::string_view str, std::string_view optional_prefix); +/** + * Sanitize an untrusted string (e.g. from the network). This will get strip + * control characters and (optionally) any MT-style escape sequences too. + * Note that they won't be removed cleanly but rather just broken, unlike with + * unescape_enriched. + * Line breaks and UTF-8 is permitted. + */ +[[nodiscard]] +std::string sanitize_untrusted(std::string_view str, bool keep_escapes = true); + /** * Prints a sanitized version of a string without control characters. * '\t' and '\n' are allowed, as are UTF-8 control characters (e.g. RTL). From 7968ab69281103bb99280e70603e01a33df92c9a Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 19 Aug 2024 21:20:20 +0200 Subject: [PATCH 022/200] Move network protocol implementation behind an interface --- src/client/client.cpp | 7 +- src/client/client.h | 8 +- src/network/CMakeLists.txt | 5 +- src/network/connection.cpp | 1671 +---------------- src/network/connection.h | 362 +--- src/network/mtp/impl.cpp | 1667 ++++++++++++++++ src/network/mtp/impl.h | 325 ++++ .../{connection_internal.h => mtp/internal.h} | 33 +- .../threads.cpp} | 2 +- .../{connectionthreads.h => mtp/threads.h} | 2 +- src/network/networkexceptions.h | 22 - src/network/networkprotocol.h | 3 - src/network/peerhandler.h | 16 +- src/server.cpp | 10 +- src/server.h | 8 +- src/server/clientiface.cpp | 3 +- src/server/clientiface.h | 6 +- src/unittest/test_connection.cpp | 13 +- 18 files changed, 2109 insertions(+), 2054 deletions(-) create mode 100644 src/network/mtp/impl.cpp create mode 100644 src/network/mtp/impl.h rename src/network/{connection_internal.h => mtp/internal.h} (95%) rename src/network/{connectionthreads.cpp => mtp/threads.cpp} (99%) rename src/network/{connectionthreads.h => mtp/threads.h} (99%) diff --git a/src/client/client.cpp b/src/client/client.cpp index af32b6db8..d15c9608a 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -392,8 +392,7 @@ void Client::connect(const Address &address, const std::string &address_name, } m_address_name = address_name; - m_con.reset(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, - address.isIPv6(), this)); + m_con.reset(con::createMTP(CONNECTION_TIMEOUT, address.isIPv6(), this)); infostream << "Connecting to server at "; address.print(infostream); @@ -866,13 +865,13 @@ bool Client::loadMedia(const std::string &data, const std::string &filename, } // Virtual methods from con::PeerHandler -void Client::peerAdded(con::Peer *peer) +void Client::peerAdded(con::IPeer *peer) { infostream << "Client::peerAdded(): peer->id=" << peer->id << std::endl; } -void Client::deletingPeer(con::Peer *peer, bool timeout) +void Client::deletingPeer(con::IPeer *peer, bool timeout) { infostream << "Client::deletingPeer(): " "Server Peer is getting deleted " diff --git a/src/client/client.h b/src/client/client.h index b2ff9a0da..64ab90eb3 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -71,7 +71,7 @@ class Camera; struct PlayerControl; class NetworkPacket; namespace con { -class Connection; +class IConnection; } using sound_handle_t = int; @@ -452,8 +452,8 @@ private: void loadMods(); // Virtual methods from con::PeerHandler - void peerAdded(con::Peer *peer) override; - void deletingPeer(con::Peer *peer, bool timeout) override; + void peerAdded(con::IPeer *peer) override; + void deletingPeer(con::IPeer *peer, bool timeout) override; void initLocalMapSaving(const Address &address, const std::string &hostname, @@ -493,7 +493,7 @@ private: std::unique_ptr m_mesh_update_manager; ClientEnvironment m_env; std::unique_ptr m_particle_manager; - std::unique_ptr m_con; + std::unique_ptr m_con; std::string m_address_name; ELoginRegister m_allow_login_or_register = ELoginRegister::Any; Camera *m_camera = nullptr; diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index d2e2f52e9..8f17e58af 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -1,10 +1,11 @@ set(common_network_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/address.cpp ${CMAKE_CURRENT_SOURCE_DIR}/connection.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/connectionthreads.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mtp/impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mtp/threads.cpp ${CMAKE_CURRENT_SOURCE_DIR}/networkpacket.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/serverpackethandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serveropcodes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/serverpackethandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/socket.cpp PARENT_SCOPE ) diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 00b4fe4b0..3ffd06fc7 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -1,1672 +1,17 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include -#include -#include -#include -#include "connection_internal.h" -#include "serialization.h" -#include "log.h" -#include "porting.h" -#include "network/connectionthreads.h" -#include "network/networkpacket.h" -#include "network/peerhandler.h" -#include "util/serialize.h" -#include "util/numeric.h" -#include "util/string.h" -#include "settings.h" -#include "profiler.h" +#include "network/connection.h" +#include "network/mtp/impl.h" namespace con { -/******************************************************************************/ -/* defines used for debugging and profiling */ -/******************************************************************************/ -#ifdef NDEBUG - #define PROFILE(a) -#else - #define PROFILE(a) a -#endif - -// TODO: Clean this up. -#define LOG(a) a - -#define PING_TIMEOUT 5.0f - -// exponent base -#define RESEND_SCALE_BASE 1.5f - -// since spacing is exponential the numbers here shouldn't be too high -// (it's okay to start out quick) -#define RESEND_TIMEOUT_MIN 0.1f -#define RESEND_TIMEOUT_MAX 2.0f -#define RESEND_TIMEOUT_FACTOR 2 - -u16 BufferedPacket::getSeqnum() const -{ - if (size() < BASE_HEADER_SIZE + 3) - return 0; // should never happen - - return readU16(&data[BASE_HEADER_SIZE + 1]); -} - -BufferedPacketPtr makePacket(const Address &address, const SharedBuffer &data, - u32 protocol_id, session_t sender_peer_id, u8 channel) -{ - u32 packet_size = data.getSize() + BASE_HEADER_SIZE; - - auto p = std::make_shared(packet_size); - p->address = address; - - writeU32(&p->data[0], protocol_id); - writeU16(&p->data[4], sender_peer_id); - writeU8(&p->data[6], channel); - - memcpy(&p->data[BASE_HEADER_SIZE], *data, data.getSize()); - - return p; -} - -SharedBuffer makeOriginalPacket(const SharedBuffer &data) -{ - u32 header_size = 1; - u32 packet_size = data.getSize() + header_size; - SharedBuffer b(packet_size); - - writeU8(&(b[0]), PACKET_TYPE_ORIGINAL); - if (data.getSize() > 0) { - memcpy(&(b[header_size]), *data, data.getSize()); - } - return b; -} - -// Split data in chunks and add TYPE_SPLIT headers to them -void makeSplitPacket(const SharedBuffer &data, u32 chunksize_max, u16 seqnum, - std::list> *chunks) -{ - // Chunk packets, containing the TYPE_SPLIT header - const u32 chunk_header_size = 7; - const u32 maximum_data_size = chunksize_max - chunk_header_size; - u32 start = 0, end = 0; - u16 chunk_num = 0; - do { - end = start + maximum_data_size - 1; - if (end > data.getSize() - 1) - end = data.getSize() - 1; - - u32 payload_size = end - start + 1; - u32 packet_size = chunk_header_size + payload_size; - - SharedBuffer chunk(packet_size); - - writeU8(&chunk[0], PACKET_TYPE_SPLIT); - writeU16(&chunk[1], seqnum); - // [3] u16 chunk_count is written at next stage - writeU16(&chunk[5], chunk_num); - memcpy(&chunk[chunk_header_size], &data[start], payload_size); - - chunks->push_back(chunk); - - start = end + 1; - sanity_check(chunk_num < 0xFFFF); // overflow - chunk_num++; - } - while (end != data.getSize() - 1); - - for (auto &chunk : *chunks) { - // Write chunk_count - writeU16(&chunk[3], chunk_num); - } -} - -void makeAutoSplitPacket(const SharedBuffer &data, u32 chunksize_max, - u16 &split_seqnum, std::list> *list) -{ - u32 original_header_size = 1; - - if (data.getSize() + original_header_size > chunksize_max) { - makeSplitPacket(data, chunksize_max, split_seqnum, list); - split_seqnum++; - return; - } - - list->push_back(makeOriginalPacket(data)); -} - -SharedBuffer makeReliablePacket(const SharedBuffer &data, u16 seqnum) -{ - u32 header_size = 3; - u32 packet_size = data.getSize() + header_size; - SharedBuffer b(packet_size); - - writeU8(&b[0], PACKET_TYPE_RELIABLE); - writeU16(&b[1], seqnum); - - memcpy(&b[header_size], *data, data.getSize()); - - return b; -} - -/* - ReliablePacketBuffer -*/ - -void ReliablePacketBuffer::print() -{ - MutexAutoLock listlock(m_list_mutex); - LOG(dout_con<<"Dump of ReliablePacketBuffer:" << std::endl); - unsigned int index = 0; - for (BufferedPacketPtr &packet : m_list) { - LOG(dout_con<getSeqnum() << std::endl); - index++; - } -} - -bool ReliablePacketBuffer::empty() -{ - MutexAutoLock listlock(m_list_mutex); - return m_list.empty(); -} - -u32 ReliablePacketBuffer::size() -{ - MutexAutoLock listlock(m_list_mutex); - return m_list.size(); -} - -ReliablePacketBuffer::FindResult ReliablePacketBuffer::findPacketNoLock(u16 seqnum) -{ - for (auto it = m_list.begin(); it != m_list.end(); ++it) { - if ((*it)->getSeqnum() == seqnum) - return it; - } - return m_list.end(); -} - -bool ReliablePacketBuffer::getFirstSeqnum(u16& result) -{ - MutexAutoLock listlock(m_list_mutex); - if (m_list.empty()) - return false; - result = m_list.front()->getSeqnum(); - return true; -} - -BufferedPacketPtr ReliablePacketBuffer::popFirst() -{ - MutexAutoLock listlock(m_list_mutex); - if (m_list.empty()) - throw NotFoundException("Buffer is empty"); - - BufferedPacketPtr p(m_list.front()); - m_list.pop_front(); - - if (m_list.empty()) { - m_oldest_non_answered_ack = 0; - } else { - m_oldest_non_answered_ack = m_list.front()->getSeqnum(); - } - return p; -} - -BufferedPacketPtr ReliablePacketBuffer::popSeqnum(u16 seqnum) +IConnection *createMTP(float timeout, bool ipv6, PeerHandler *handler) { - MutexAutoLock listlock(m_list_mutex); - auto r = findPacketNoLock(seqnum); - if (r == m_list.end()) { - LOG(dout_con<<"Sequence number: " << seqnum - << " not found in reliable buffer"<getSeqnum(); - } - return p; + // safe minimum across internet networks for ipv4 and ipv6 + constexpr u32 MAX_PACKET_SIZE = 512; + return new con::Connection(MAX_PACKET_SIZE, timeout, ipv6, handler); } -void ReliablePacketBuffer::insert(BufferedPacketPtr &p_ptr, u16 next_expected) -{ - MutexAutoLock listlock(m_list_mutex); - const BufferedPacket &p = *p_ptr; - - if (p.size() < BASE_HEADER_SIZE + 3) { - errorstream << "ReliablePacketBuffer::insert(): Invalid data size for " - "reliable packet" << std::endl; - return; - } - u8 type = readU8(&p.data[BASE_HEADER_SIZE + 0]); - if (type != PACKET_TYPE_RELIABLE) { - errorstream << "ReliablePacketBuffer::insert(): type is not reliable" - << std::endl; - return; - } - const u16 seqnum = p.getSeqnum(); - - if (!seqnum_in_window(seqnum, next_expected, MAX_RELIABLE_WINDOW_SIZE)) { - errorstream << "ReliablePacketBuffer::insert(): seqnum is outside of " - "expected window " << std::endl; - return; - } - if (seqnum == next_expected) { - errorstream << "ReliablePacketBuffer::insert(): seqnum is next expected" - << std::endl; - return; - } - - sanity_check(m_list.size() <= SEQNUM_MAX); // FIXME: Handle the error? - - // Find the right place for the packet and insert it there - // If list is empty, just add it - if (m_list.empty()) { - m_list.push_back(p_ptr); - m_oldest_non_answered_ack = seqnum; - // Done. - return; - } - - // Otherwise find the right place - auto it = m_list.begin(); - // Find the first packet in the list which has a higher seqnum - u16 s = (*it)->getSeqnum(); - - /* case seqnum is smaller then next_expected seqnum */ - /* this is true e.g. on wrap around */ - if (seqnum < next_expected) { - while(((s < seqnum) || (s >= next_expected)) && (it != m_list.end())) { - ++it; - if (it != m_list.end()) - s = (*it)->getSeqnum(); - } - } - /* non wrap around case (at least for incoming and next_expected */ - else - { - while(((s < seqnum) && (s >= next_expected)) && (it != m_list.end())) { - ++it; - if (it != m_list.end()) - s = (*it)->getSeqnum(); - } - } - - if (s == seqnum) { - /* nothing to do this seems to be a resent packet */ - /* for paranoia reason data should be compared */ - auto &i = *it; - if ( - (i->getSeqnum() != seqnum) || - (i->size() != p.size()) || - (i->address != p.address) - ) - { - /* if this happens your maximum transfer window may be to big */ - char buf[200]; - snprintf(buf, sizeof(buf), - "Duplicated seqnum %d non matching packet detected:\n", - seqnum); - warningstream << buf; - snprintf(buf, sizeof(buf), - "Old: seqnum: %05d size: %04zu, address: %s\n", - i->getSeqnum(), i->size(), - i->address.serializeString().c_str()); - warningstream << buf; - snprintf(buf, sizeof(buf), - "New: seqnum: %05d size: %04zu, address: %s\n", - p.getSeqnum(), p.size(), - p.address.serializeString().c_str()); - warningstream << buf << std::flush; - throw IncomingDataCorruption("duplicated packet isn't same as original one"); - } - } - /* insert or push back */ - else if (it != m_list.end()) { - m_list.insert(it, p_ptr); - } else { - m_list.push_back(p_ptr); - } - - /* update last packet number */ - m_oldest_non_answered_ack = m_list.front()->getSeqnum(); } - -void ReliablePacketBuffer::incrementTimeouts(float dtime) -{ - MutexAutoLock listlock(m_list_mutex); - for (auto &packet : m_list) { - packet->time += dtime; - packet->totaltime += dtime; - } -} - -u32 ReliablePacketBuffer::getTimedOuts(float timeout) -{ - MutexAutoLock listlock(m_list_mutex); - u32 count = 0; - for (auto &packet : m_list) { - if (packet->totaltime >= timeout) - count++; - } - return count; -} - -std::vector> - ReliablePacketBuffer::getResend(float timeout, u32 max_packets) -{ - MutexAutoLock listlock(m_list_mutex); - std::vector> timed_outs; - for (auto &packet : m_list) { - // resend time scales exponentially with each cycle - const float pkt_timeout = timeout * powf(RESEND_SCALE_BASE, packet->resend_count); - - if (packet->time < pkt_timeout) - continue; - - // caller will resend packet so reset time and increase counter - packet->time = 0.0f; - packet->resend_count++; - - timed_outs.emplace_back(packet); - - if (timed_outs.size() >= max_packets) - break; - } - return timed_outs; -} - -/* - IncomingSplitPacket -*/ - -bool IncomingSplitPacket::insert(u32 chunk_num, SharedBuffer &chunkdata) -{ - sanity_check(chunk_num < chunk_count); - - // If chunk already exists, ignore it. - // Sometimes two identical packets may arrive when there is network - // lag and the server re-sends stuff. - if (chunks.find(chunk_num) != chunks.end()) - return false; - - // Set chunk data in buffer - chunks[chunk_num] = chunkdata; - - return true; -} - -SharedBuffer IncomingSplitPacket::reassemble() -{ - sanity_check(allReceived()); - - // Calculate total size - u32 totalsize = 0; - for (const auto &chunk : chunks) - totalsize += chunk.second.getSize(); - - SharedBuffer fulldata(totalsize); - - // Copy chunks to data buffer - u32 start = 0; - for (u32 chunk_i = 0; chunk_i < chunk_count; chunk_i++) { - const SharedBuffer &buf = chunks[chunk_i]; - memcpy(&fulldata[start], *buf, buf.getSize()); - start += buf.getSize(); - } - - return fulldata; -} - -/* - IncomingSplitBuffer -*/ - -IncomingSplitBuffer::~IncomingSplitBuffer() -{ - MutexAutoLock listlock(m_map_mutex); - for (auto &i : m_buf) { - delete i.second; - } -} - -SharedBuffer IncomingSplitBuffer::insert(BufferedPacketPtr &p_ptr, bool reliable) -{ - MutexAutoLock listlock(m_map_mutex); - const BufferedPacket &p = *p_ptr; - - u32 headersize = BASE_HEADER_SIZE + 7; - if (p.size() < headersize) { - errorstream << "Invalid data size for split packet" << std::endl; - return SharedBuffer(); - } - u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]); - u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); - u16 chunk_count = readU16(&p.data[BASE_HEADER_SIZE+3]); - u16 chunk_num = readU16(&p.data[BASE_HEADER_SIZE+5]); - - if (type != PACKET_TYPE_SPLIT) { - errorstream << "IncomingSplitBuffer::insert(): type is not split" - << std::endl; - return SharedBuffer(); - } - if (chunk_num >= chunk_count) { - errorstream << "IncomingSplitBuffer::insert(): chunk_num=" << chunk_num - << " >= chunk_count=" << chunk_count << std::endl; - return SharedBuffer(); - } - - // Add if doesn't exist - IncomingSplitPacket *sp; - if (m_buf.find(seqnum) == m_buf.end()) { - sp = new IncomingSplitPacket(chunk_count, reliable); - m_buf[seqnum] = sp; - } else { - sp = m_buf[seqnum]; - } - - if (chunk_count != sp->chunk_count) { - errorstream << "IncomingSplitBuffer::insert(): chunk_count=" - << chunk_count << " != sp->chunk_count=" << sp->chunk_count - << std::endl; - return SharedBuffer(); - } - if (reliable != sp->reliable) - LOG(derr_con<<"Connection: WARNING: reliable="<reliable="<reliable - < chunkdata(chunkdatasize); - memcpy(*chunkdata, &(p.data[headersize]), chunkdatasize); - - if (!sp->insert(chunk_num, chunkdata)) - return SharedBuffer(); - - // If not all chunks are received, return empty buffer - if (!sp->allReceived()) - return SharedBuffer(); - - SharedBuffer fulldata = sp->reassemble(); - - // Remove sp from buffer - m_buf.erase(seqnum); - delete sp; - - return fulldata; -} - -void IncomingSplitBuffer::removeUnreliableTimedOuts(float dtime, float timeout) -{ - MutexAutoLock listlock(m_map_mutex); - std::vector remove_queue; - { - for (const auto &i : m_buf) { - IncomingSplitPacket *p = i.second; - // Reliable ones are not removed by timeout - if (p->reliable) - continue; - p->time += dtime; - if (p->time >= timeout) - remove_queue.push_back(i.first); - } - } - for (u16 j : remove_queue) { - LOG(dout_con<<"NOTE: Removing timed out unreliable split packet"<second; - m_buf.erase(it); - } -} - -/* - ConnectionCommand - */ - -ConnectionCommandPtr ConnectionCommand::create(ConnectionCommandType type) -{ - return ConnectionCommandPtr(new ConnectionCommand(type)); -} - -ConnectionCommandPtr ConnectionCommand::serve(Address address) -{ - auto c = create(CONNCMD_SERVE); - c->address = address; - return c; -} - -ConnectionCommandPtr ConnectionCommand::connect(Address address) -{ - auto c = create(CONNCMD_CONNECT); - c->address = address; - return c; -} - -ConnectionCommandPtr ConnectionCommand::disconnect() -{ - return create(CONNCMD_DISCONNECT); -} - -ConnectionCommandPtr ConnectionCommand::disconnect_peer(session_t peer_id) -{ - auto c = create(CONNCMD_DISCONNECT_PEER); - c->peer_id = peer_id; - return c; -} - -ConnectionCommandPtr ConnectionCommand::resend_one(session_t peer_id) -{ - auto c = create(CONNCMD_RESEND_ONE); - c->peer_id = peer_id; - c->channelnum = 0; // must be same as createPeer - c->reliable = true; - return c; -} - -ConnectionCommandPtr ConnectionCommand::send(session_t peer_id, u8 channelnum, - NetworkPacket *pkt, bool reliable) -{ - auto c = create(CONNCMD_SEND); - c->peer_id = peer_id; - c->channelnum = channelnum; - c->reliable = reliable; - c->data = pkt->oldForgePacket(); - return c; -} - -ConnectionCommandPtr ConnectionCommand::ack(session_t peer_id, u8 channelnum, const Buffer &data) -{ - auto c = create(CONCMD_ACK); - c->peer_id = peer_id; - c->channelnum = channelnum; - c->reliable = false; - data.copyTo(c->data); - return c; -} - -ConnectionCommandPtr ConnectionCommand::createPeer(session_t peer_id, const Buffer &data) -{ - auto c = create(CONCMD_CREATE_PEER); - c->peer_id = peer_id; - c->channelnum = 0; - c->reliable = true; - c->raw = true; - data.copyTo(c->data); - return c; -} - -/* - Channel -*/ - -u16 Channel::readNextIncomingSeqNum() -{ - MutexAutoLock internal(m_internal_mutex); - return next_incoming_seqnum; -} - -u16 Channel::incNextIncomingSeqNum() -{ - MutexAutoLock internal(m_internal_mutex); - u16 retval = next_incoming_seqnum; - next_incoming_seqnum++; - return retval; -} - -u16 Channel::readNextSplitSeqNum() -{ - MutexAutoLock internal(m_internal_mutex); - return next_outgoing_split_seqnum; -} -void Channel::setNextSplitSeqNum(u16 seqnum) -{ - MutexAutoLock internal(m_internal_mutex); - next_outgoing_split_seqnum = seqnum; -} - -u16 Channel::getOutgoingSequenceNumber(bool& successful) -{ - MutexAutoLock internal(m_internal_mutex); - - u16 retval = next_outgoing_seqnum; - successful = false; - - /* shortcut if there ain't any packet in outgoing list */ - if (outgoing_reliables_sent.empty()) { - successful = true; - next_outgoing_seqnum++; - return retval; - } - - u16 lowest_unacked_seqnumber; - if (outgoing_reliables_sent.getFirstSeqnum(lowest_unacked_seqnumber)) { - if (lowest_unacked_seqnumber < next_outgoing_seqnum) { - // ugly cast but this one is required in order to tell compiler we - // know about difference of two unsigned may be negative in general - // but we already made sure it won't happen in this case - if (((u16)(next_outgoing_seqnum - lowest_unacked_seqnumber)) > m_window_size) { - return 0; - } - } else { - // ugly cast but this one is required in order to tell compiler we - // know about difference of two unsigned may be negative in general - // but we already made sure it won't happen in this case - if ((next_outgoing_seqnum + (u16)(SEQNUM_MAX - lowest_unacked_seqnumber)) > - m_window_size) { - return 0; - } - } - } - - successful = true; - next_outgoing_seqnum++; - return retval; -} - -u16 Channel::readOutgoingSequenceNumber() -{ - MutexAutoLock internal(m_internal_mutex); - return next_outgoing_seqnum; -} - -bool Channel::putBackSequenceNumber(u16 seqnum) -{ - if (((seqnum + 1) % (SEQNUM_MAX+1)) == next_outgoing_seqnum) { - - next_outgoing_seqnum = seqnum; - return true; - } - return false; -} - -void Channel::UpdateBytesSent(unsigned int bytes, unsigned int packets) -{ - MutexAutoLock internal(m_internal_mutex); - current_bytes_transfered += bytes; - current_packet_successful += packets; -} - -void Channel::UpdateBytesReceived(unsigned int bytes) { - MutexAutoLock internal(m_internal_mutex); - current_bytes_received += bytes; -} - -void Channel::UpdateBytesLost(unsigned int bytes) -{ - MutexAutoLock internal(m_internal_mutex); - current_bytes_lost += bytes; -} - - -void Channel::UpdatePacketLossCounter(unsigned int count) -{ - MutexAutoLock internal(m_internal_mutex); - current_packet_loss += count; -} - -void Channel::UpdatePacketTooLateCounter() -{ - MutexAutoLock internal(m_internal_mutex); - current_packet_too_late++; -} - -void Channel::UpdateTimers(float dtime) -{ - bpm_counter += dtime; - packet_loss_counter += dtime; - - if (packet_loss_counter > 1.0f) { - packet_loss_counter -= 1.0f; - - unsigned int packet_loss = 11; /* use a neutral value for initialization */ - unsigned int packets_successful = 0; - //unsigned int packet_too_late = 0; - - bool reasonable_amount_of_data_transmitted = false; - - { - MutexAutoLock internal(m_internal_mutex); - packet_loss = current_packet_loss; - //packet_too_late = current_packet_too_late; - packets_successful = current_packet_successful; - - if (current_bytes_transfered > (unsigned int) (m_window_size*512/2)) { - reasonable_amount_of_data_transmitted = true; - } - current_packet_loss = 0; - current_packet_too_late = 0; - current_packet_successful = 0; - } - - /* dynamic window size */ - float successful_to_lost_ratio = 0.0f; - bool done = false; - - if (packets_successful > 0) { - successful_to_lost_ratio = packet_loss/packets_successful; - } else if (packet_loss > 0) { - setWindowSize(m_window_size - 10); - done = true; - } - - if (!done) { - if (successful_to_lost_ratio < 0.01f) { - /* don't even think about increasing if we didn't even - * use major parts of our window */ - if (reasonable_amount_of_data_transmitted) - setWindowSize(m_window_size + 100); - } else if (successful_to_lost_ratio < 0.05f) { - /* don't even think about increasing if we didn't even - * use major parts of our window */ - if (reasonable_amount_of_data_transmitted) - setWindowSize(m_window_size + 50); - } else if (successful_to_lost_ratio > 0.15f) { - setWindowSize(m_window_size - 100); - } else if (successful_to_lost_ratio > 0.1f) { - setWindowSize(m_window_size - 50); - } - } - } - - if (bpm_counter > 10.0f) { - { - MutexAutoLock internal(m_internal_mutex); - cur_kbps = - (((float) current_bytes_transfered)/bpm_counter)/1024.0f; - current_bytes_transfered = 0; - cur_kbps_lost = - (((float) current_bytes_lost)/bpm_counter)/1024.0f; - current_bytes_lost = 0; - cur_incoming_kbps = - (((float) current_bytes_received)/bpm_counter)/1024.0f; - current_bytes_received = 0; - bpm_counter = 0.0f; - } - - if (cur_kbps > max_kbps) { - max_kbps = cur_kbps; - } - - if (cur_kbps_lost > max_kbps_lost) { - max_kbps_lost = cur_kbps_lost; - } - - if (cur_incoming_kbps > max_incoming_kbps) { - max_incoming_kbps = cur_incoming_kbps; - } - - rate_samples = MYMIN(rate_samples+1,10); - float old_fraction = ((float) (rate_samples-1) )/( (float) rate_samples); - avg_kbps = avg_kbps * old_fraction + - cur_kbps * (1.0 - old_fraction); - avg_kbps_lost = avg_kbps_lost * old_fraction + - cur_kbps_lost * (1.0 - old_fraction); - avg_incoming_kbps = avg_incoming_kbps * old_fraction + - cur_incoming_kbps * (1.0 - old_fraction); - } -} - - -/* - Peer -*/ - -PeerHelper::~PeerHelper() -{ - if (m_peer) - m_peer->DecUseCount(); - - m_peer = nullptr; -} - -PeerHelper& PeerHelper::operator=(Peer* peer) -{ - if (m_peer) - m_peer->DecUseCount(); - m_peer = peer; - if (peer && !peer->IncUseCount()) - m_peer = nullptr; - return *this; -} - -bool Peer::IncUseCount() -{ - MutexAutoLock lock(m_exclusive_access_mutex); - - if (!m_pending_deletion) { - this->m_usage++; - return true; - } - - return false; -} - -void Peer::DecUseCount() -{ - { - MutexAutoLock lock(m_exclusive_access_mutex); - sanity_check(m_usage > 0); - m_usage--; - - if (!((m_pending_deletion) && (m_usage == 0))) - return; - } - delete this; -} - -void Peer::RTTStatistics(float rtt, const std::string &profiler_id, - unsigned int num_samples) { - - if (m_last_rtt > 0) { - /* set min max values */ - if (rtt < m_rtt.min_rtt) - m_rtt.min_rtt = rtt; - if (rtt >= m_rtt.max_rtt) - m_rtt.max_rtt = rtt; - - /* do average calculation */ - if (m_rtt.avg_rtt < 0.0) - m_rtt.avg_rtt = rtt; - else - m_rtt.avg_rtt = m_rtt.avg_rtt * (num_samples/(num_samples-1)) + - rtt * (1/num_samples); - - /* do jitter calculation */ - - //just use some neutral value at beginning - float jitter = m_rtt.jitter_min; - - if (rtt > m_last_rtt) - jitter = rtt-m_last_rtt; - - if (rtt <= m_last_rtt) - jitter = m_last_rtt - rtt; - - if (jitter < m_rtt.jitter_min) - m_rtt.jitter_min = jitter; - if (jitter >= m_rtt.jitter_max) - m_rtt.jitter_max = jitter; - - if (m_rtt.jitter_avg < 0.0) - m_rtt.jitter_avg = jitter; - else - m_rtt.jitter_avg = m_rtt.jitter_avg * (num_samples/(num_samples-1)) + - jitter * (1/num_samples); - - if (!profiler_id.empty()) { - g_profiler->graphAdd(profiler_id + " RTT [ms]", rtt * 1000.f); - g_profiler->graphAdd(profiler_id + " jitter [ms]", jitter * 1000.f); - } - } - /* save values required for next loop */ - m_last_rtt = rtt; -} - -bool Peer::isTimedOut(float timeout, std::string &reason) -{ - MutexAutoLock lock(m_exclusive_access_mutex); - - { - u64 current_time = porting::getTimeMs(); - float dtime = CALC_DTIME(m_last_timeout_check, current_time); - m_last_timeout_check = current_time; - m_timeout_counter += dtime; - } - if (m_timeout_counter > timeout) { - reason = "timeout counter"; - return true; - } - - return false; -} - -void Peer::Drop() -{ - { - MutexAutoLock usage_lock(m_exclusive_access_mutex); - m_pending_deletion = true; - if (m_usage != 0) - return; - } - - PROFILE(std::stringstream peerIdentifier1); - PROFILE(peerIdentifier1 << "runTimeouts[" << m_connection->getDesc() - << ";" << id << ";RELIABLE]"); - PROFILE(g_profiler->remove(peerIdentifier1.str())); - PROFILE(std::stringstream peerIdentifier2); - PROFILE(peerIdentifier2 << "sendPackets[" << m_connection->getDesc() - << ";" << id << ";RELIABLE]"); - PROFILE(ScopeProfiler peerprofiler(g_profiler, peerIdentifier2.str(), SPT_AVG)); - - delete this; -} - -UDPPeer::UDPPeer(session_t id, const Address &address, Connection *connection) : - Peer(id, address, connection) -{ - for (Channel &channel : channels) - channel.setWindowSize(START_RELIABLE_WINDOW_SIZE); -} - -bool UDPPeer::isTimedOut(float timeout, std::string &reason) -{ - if (Peer::isTimedOut(timeout, reason)) - return true; - - MutexAutoLock lock(m_exclusive_access_mutex); - - for (int i = 0; i < CHANNEL_COUNT; i++) { - Channel &channel = channels[i]; - if (channel.outgoing_reliables_sent.getTimedOuts(timeout) > 0) { - reason = "outgoing reliables channel=" + itos(i); - return true; - } - } - - return false; -} - -void UDPPeer::reportRTT(float rtt) -{ - if (rtt < 0.0) { - return; - } - RTTStatistics(rtt,"rudp",MAX_RELIABLE_WINDOW_SIZE*10); - - // use this value to decide the resend timeout - float timeout = getStat(AVG_RTT) * RESEND_TIMEOUT_FACTOR; - if (timeout < RESEND_TIMEOUT_MIN) - timeout = RESEND_TIMEOUT_MIN; - if (timeout > RESEND_TIMEOUT_MAX) - timeout = RESEND_TIMEOUT_MAX; - - setResendTimeout(timeout); -} - -bool UDPPeer::Ping(float dtime,SharedBuffer& data) -{ - m_ping_timer += dtime; - if (!isHalfOpen() && m_ping_timer >= PING_TIMEOUT) - { - // Create and send PING packet - writeU8(&data[0], PACKET_TYPE_CONTROL); - writeU8(&data[1], CONTROLTYPE_PING); - m_ping_timer = 0.0f; - return true; - } - return false; -} - -void UDPPeer::PutReliableSendCommand(ConnectionCommandPtr &c, - unsigned int max_packet_size) -{ - if (m_pending_disconnect) - return; - - Channel &chan = channels[c->channelnum]; - - if (chan.queued_commands.empty() && - /* don't queue more packets then window size */ - (chan.queued_reliables.size() + 1 < chan.getWindowSize() / 2)) { - LOG(dout_con<getDesc() - <<" processing reliable command for peer id: " << c->peer_id - <<" data size: " << c->data.getSize() << std::endl); - if (processReliableSendCommand(c, max_packet_size)) - return; - } else { - LOG(dout_con<getDesc() - <<" Queueing reliable command for peer id: " << c->peer_id - <<" data size: " << c->data.getSize() <= chan.getWindowSize() / 2) { - LOG(derr_con << m_connection->getDesc() - << "Possible packet stall to peer id: " << c->peer_id - << " queued_commands=" << chan.queued_commands.size() - << std::endl); - } - } - chan.queued_commands.push_back(c); -} - -bool UDPPeer::processReliableSendCommand( - ConnectionCommandPtr &c_ptr, - unsigned int max_packet_size) -{ - if (m_pending_disconnect) - return true; - - const auto &c = *c_ptr; - Channel &chan = channels[c.channelnum]; - - const u32 chunksize_max = max_packet_size - - BASE_HEADER_SIZE - - RELIABLE_HEADER_SIZE; - - std::list> originals; - - if (c.raw) { - originals.emplace_back(c.data); - } else { - u16 split_seqnum = chan.readNextSplitSeqNum(); - makeAutoSplitPacket(c.data, chunksize_max, split_seqnum, &originals); - chan.setNextSplitSeqNum(split_seqnum); - } - - sanity_check(originals.size() < MAX_RELIABLE_WINDOW_SIZE); - - bool have_sequence_number = false; - bool have_initial_sequence_number = false; - std::queue toadd; - u16 initial_sequence_number = 0; - - for (SharedBuffer &original : originals) { - u16 seqnum = chan.getOutgoingSequenceNumber(have_sequence_number); - - /* oops, we don't have enough sequence numbers to send this packet */ - if (!have_sequence_number) - break; - - if (!have_initial_sequence_number) - { - initial_sequence_number = seqnum; - have_initial_sequence_number = true; - } - - SharedBuffer reliable = makeReliablePacket(original, seqnum); - - // Add base headers and make a packet - BufferedPacketPtr p = con::makePacket(address, reliable, - m_connection->GetProtocolID(), m_connection->GetPeerID(), - c.channelnum); - - toadd.push(p); - } - - if (have_sequence_number) { - while (!toadd.empty()) { - BufferedPacketPtr p = toadd.front(); - toadd.pop(); -// LOG(dout_con<getDesc() -// << " queuing reliable packet for peer_id: " << c.peer_id -// << " channel: " << (c.channelnum&0xFF) -// << " seqnum: " << readU16(&p.data[BASE_HEADER_SIZE+1]) -// << std::endl) - chan.queued_reliables.push(p); - } - sanity_check(chan.queued_reliables.size() < 0xFFFF); - return true; - } - - u16 packets_available = toadd.size(); - /* we didn't get a single sequence number no need to fill queue */ - if (!have_initial_sequence_number) { - LOG(derr_con << m_connection->getDesc() << "Ran out of sequence numbers!" << std::endl); - return false; - } - - while (!toadd.empty()) { - /* remove packet */ - toadd.pop(); - - bool successfully_put_back_sequence_number - = chan.putBackSequenceNumber( - (initial_sequence_number+toadd.size() % (SEQNUM_MAX+1))); - - FATAL_ERROR_IF(!successfully_put_back_sequence_number, "error"); - } - - u32 n_queued = chan.outgoing_reliables_sent.size(); - - LOG(dout_con<getDesc() - << " Windowsize exceeded on reliable sending " - << c.data.getSize() << " bytes" - << std::endl << "\t\tinitial_sequence_number: " - << initial_sequence_number - << std::endl << "\t\tgot at most : " - << packets_available << " packets" - << std::endl << "\t\tpackets queued : " - << n_queued - << std::endl); - - return false; -} - -void UDPPeer::RunCommandQueues( - unsigned int max_packet_size, - unsigned int maxtransfer) -{ - - for (Channel &channel : channels) { - - if ((!channel.queued_commands.empty()) && - (channel.queued_reliables.size() < maxtransfer)) { - try { - ConnectionCommandPtr c = channel.queued_commands.front(); - - LOG(dout_con << m_connection->getDesc() - << " processing queued reliable command " << std::endl); - - // Packet is processed, remove it from queue - if (processReliableSendCommand(c, max_packet_size)) { - channel.queued_commands.pop_front(); - } else { - LOG(dout_con << m_connection->getDesc() - << " Failed to queue packets for peer_id: " << c->peer_id - << ", delaying sending of " << c->data.getSize() - << " bytes" << std::endl); - } - } - catch (ItemNotFoundException &e) { - // intentionally empty - } - } - } -} - -u16 UDPPeer::getNextSplitSequenceNumber(u8 channel) -{ - assert(channel < CHANNEL_COUNT); // Pre-condition - return channels[channel].readNextSplitSeqNum(); -} - -void UDPPeer::setNextSplitSequenceNumber(u8 channel, u16 seqnum) -{ - assert(channel < CHANNEL_COUNT); // Pre-condition - channels[channel].setNextSplitSeqNum(seqnum); -} - -SharedBuffer UDPPeer::addSplitPacket(u8 channel, BufferedPacketPtr &toadd, - bool reliable) -{ - assert(channel < CHANNEL_COUNT); // Pre-condition - return channels[channel].incoming_splits.insert(toadd, reliable); -} - -/* - ConnectionEvent -*/ - -const char *ConnectionEvent::describe() const -{ - switch(type) { - case CONNEVENT_NONE: - return "CONNEVENT_NONE"; - case CONNEVENT_DATA_RECEIVED: - return "CONNEVENT_DATA_RECEIVED"; - case CONNEVENT_PEER_ADDED: - return "CONNEVENT_PEER_ADDED"; - case CONNEVENT_PEER_REMOVED: - return "CONNEVENT_PEER_REMOVED"; - case CONNEVENT_BIND_FAILED: - return "CONNEVENT_BIND_FAILED"; - } - return "Invalid ConnectionEvent"; -} - - -ConnectionEventPtr ConnectionEvent::create(ConnectionEventType type) -{ - return std::shared_ptr(new ConnectionEvent(type)); -} - -ConnectionEventPtr ConnectionEvent::dataReceived(session_t peer_id, const Buffer &data) -{ - auto e = create(CONNEVENT_DATA_RECEIVED); - e->peer_id = peer_id; - data.copyTo(e->data); - return e; -} - -ConnectionEventPtr ConnectionEvent::peerAdded(session_t peer_id, Address address) -{ - auto e = create(CONNEVENT_PEER_ADDED); - e->peer_id = peer_id; - e->address = address; - return e; -} - -ConnectionEventPtr ConnectionEvent::peerRemoved(session_t peer_id, bool is_timeout, Address address) -{ - auto e = create(CONNEVENT_PEER_REMOVED); - e->peer_id = peer_id; - e->timeout = is_timeout; - e->address = address; - return e; -} - -ConnectionEventPtr ConnectionEvent::bindFailed() -{ - return create(CONNEVENT_BIND_FAILED); -} - -/* - Connection -*/ - -Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout, - bool ipv6, PeerHandler *peerhandler) : - m_udpSocket(ipv6), - m_protocol_id(protocol_id), - m_sendThread(new ConnectionSendThread(max_packet_size, timeout)), - m_receiveThread(new ConnectionReceiveThread()), - m_bc_peerhandler(peerhandler) - -{ - /* Amount of time Receive() will wait for data, this is entirely different - * from the connection timeout */ - m_udpSocket.setTimeoutMs(500); - - m_sendThread->setParent(this); - m_receiveThread->setParent(this); - - m_sendThread->start(); - m_receiveThread->start(); -} - - -Connection::~Connection() -{ - m_shutting_down = true; - // request threads to stop - m_sendThread->stop(); - m_receiveThread->stop(); - - //TODO for some unkonwn reason send/receive threads do not exit as they're - // supposed to be but wait on peer timeout. To speed up shutdown we reduce - // timeout to half a second. - m_sendThread->setPeerTimeout(0.5); - - // wait for threads to finish - m_sendThread->wait(); - m_receiveThread->wait(); - - // Delete peers - for (auto &peer : m_peers) { - delete peer.second; - } -} - -/* Internal stuff */ - -void Connection::putEvent(ConnectionEventPtr e) -{ - assert(e->type != CONNEVENT_NONE); // Pre-condition - m_event_queue.push_back(e); -} - -void Connection::TriggerSend() -{ - m_sendThread->Trigger(); -} - -PeerHelper Connection::getPeerNoEx(session_t peer_id) -{ - MutexAutoLock peerlock(m_peers_mutex); - std::map::iterator node = m_peers.find(peer_id); - - if (node == m_peers.end()) { - return PeerHelper(NULL); - } - - // Error checking - FATAL_ERROR_IF(node->second->id != peer_id, "Invalid peer id"); - - return PeerHelper(node->second); -} - -/* find peer_id for address */ -session_t Connection::lookupPeer(const Address& sender) -{ - MutexAutoLock peerlock(m_peers_mutex); - for (auto &it: m_peers) { - Peer *peer = it.second; - if (peer->isPendingDeletion()) - continue; - - if (peer->getAddress() == sender) - return peer->id; - } - - return PEER_ID_INEXISTENT; -} - -u32 Connection::getActiveCount() -{ - MutexAutoLock peerlock(m_peers_mutex); - u32 count = 0; - for (auto &it : m_peers) { - Peer *peer = it.second; - if (peer->isPendingDeletion()) - continue; - if (peer->isHalfOpen()) - continue; - count++; - } - return count; -} - -bool Connection::deletePeer(session_t peer_id, bool timeout) -{ - Peer *peer = 0; - - /* lock list as short as possible */ - { - MutexAutoLock peerlock(m_peers_mutex); - if (m_peers.find(peer_id) == m_peers.end()) - return false; - peer = m_peers[peer_id]; - m_peers.erase(peer_id); - auto it = std::find(m_peer_ids.begin(), m_peer_ids.end(), peer_id); - m_peer_ids.erase(it); - } - - // Create event - putEvent(ConnectionEvent::peerRemoved(peer_id, timeout, peer->getAddress())); - - peer->Drop(); - return true; -} - -/* Interface */ - -ConnectionEventPtr Connection::waitEvent(u32 timeout_ms) -{ - try { - return m_event_queue.pop_front(timeout_ms); - } catch(ItemNotFoundException &ex) { - return ConnectionEvent::create(CONNEVENT_NONE); - } -} - -void Connection::putCommand(ConnectionCommandPtr c) -{ - if (!m_shutting_down) { - m_command_queue.push_back(c); - m_sendThread->Trigger(); - } -} - -void Connection::Serve(Address bind_addr) -{ - putCommand(ConnectionCommand::serve(bind_addr)); -} - -void Connection::Connect(Address address) -{ - putCommand(ConnectionCommand::connect(address)); -} - -bool Connection::Connected() -{ - MutexAutoLock peerlock(m_peers_mutex); - - if (m_peers.size() != 1) - return false; - - std::map::iterator node = m_peers.find(PEER_ID_SERVER); - if (node == m_peers.end()) - return false; - - if (m_peer_id == PEER_ID_INEXISTENT) - return false; - - return true; -} - -void Connection::Disconnect() -{ - putCommand(ConnectionCommand::disconnect()); -} - -bool Connection::ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms) -{ - /* - Note that this function can potentially wait infinitely if non-data - events keep happening before the timeout expires. - This is not considered to be a problem (is it?) - */ - for(;;) { - ConnectionEventPtr e_ptr = waitEvent(timeout_ms); - const ConnectionEvent &e = *e_ptr; - - if (e.type != CONNEVENT_NONE) { - LOG(dout_con << getDesc() << ": Receive: got event: " - << e.describe() << std::endl); - } - - switch (e.type) { - case CONNEVENT_NONE: - return false; - case CONNEVENT_DATA_RECEIVED: - // Data size is lesser than command size, ignoring packet - if (e.data.getSize() < 2) { - continue; - } - - pkt->putRawPacket(*e.data, e.data.getSize(), e.peer_id); - return true; - case CONNEVENT_PEER_ADDED: { - UDPPeer tmp(e.peer_id, e.address, this); - if (m_bc_peerhandler) - m_bc_peerhandler->peerAdded(&tmp); - continue; - } - case CONNEVENT_PEER_REMOVED: { - UDPPeer tmp(e.peer_id, e.address, this); - if (m_bc_peerhandler) - m_bc_peerhandler->deletingPeer(&tmp, e.timeout); - continue; - } - case CONNEVENT_BIND_FAILED: - throw ConnectionBindFailed("Failed to bind socket " - "(port already in use?)"); - } - } - return false; -} - -void Connection::Receive(NetworkPacket *pkt) -{ - bool any = ReceiveTimeoutMs(pkt, m_bc_receive_timeout); - if (!any) - throw NoIncomingDataException("No incoming data"); -} - -bool Connection::TryReceive(NetworkPacket *pkt) -{ - return ReceiveTimeoutMs(pkt, 0); -} - -void Connection::Send(session_t peer_id, u8 channelnum, - NetworkPacket *pkt, bool reliable) -{ - assert(channelnum < CHANNEL_COUNT); // Pre-condition - - // approximate check similar to UDPPeer::processReliableSendCommand() - // to get nicer errors / backtraces if this happens. - if (reliable && pkt->getSize() > MAX_RELIABLE_WINDOW_SIZE*512) { - std::ostringstream oss; - oss << "Packet too big for window, peer_id=" << peer_id - << " command=" << pkt->getCommand() << " size=" << pkt->getSize(); - FATAL_ERROR(oss.str().c_str()); - } - - putCommand(ConnectionCommand::send(peer_id, channelnum, pkt, reliable)); -} - -Address Connection::GetPeerAddress(session_t peer_id) -{ - PeerHelper peer = getPeerNoEx(peer_id); - - if (!peer) - throw PeerNotFoundException("No address for peer found!"); - return peer->getAddress(); -} - -float Connection::getPeerStat(session_t peer_id, rtt_stat_type type) -{ - PeerHelper peer = getPeerNoEx(peer_id); - if (!peer) - return -1; - return peer->getStat(type); -} - -float Connection::getLocalStat(rate_stat_type type) -{ - PeerHelper peer = getPeerNoEx(PEER_ID_SERVER); - - FATAL_ERROR_IF(!peer, "Connection::getLocalStat we couldn't get our own peer? are you serious???"); - - float retval = 0; - - for (Channel &channel : dynamic_cast(&peer)->channels) { - switch(type) { - case CUR_DL_RATE: - retval += channel.getCurrentDownloadRateKB(); - break; - case AVG_DL_RATE: - retval += channel.getAvgDownloadRateKB(); - break; - case CUR_INC_RATE: - retval += channel.getCurrentIncomingRateKB(); - break; - case AVG_INC_RATE: - retval += channel.getAvgIncomingRateKB(); - break; - case AVG_LOSS_RATE: - retval += channel.getAvgLossRateKB(); - break; - case CUR_LOSS_RATE: - retval += channel.getCurrentLossRateKB(); - break; - default: - FATAL_ERROR("Connection::getLocalStat Invalid stat type"); - } - } - return retval; -} - -session_t Connection::createPeer(const Address &sender, int fd) -{ - // Somebody wants to make a new connection - - // Get a unique peer id - const session_t minimum = 2; - const session_t overflow = MAX_UDP_PEERS; - - /* - Find an unused peer id - */ - - MutexAutoLock lock(m_peers_mutex); - session_t peer_id_new; - for (int tries = 0; tries < 100; tries++) { - peer_id_new = myrand_range(minimum, overflow - 1); - if (m_peers.find(peer_id_new) == m_peers.end()) - break; - } - if (m_peers.find(peer_id_new) != m_peers.end()) { - errorstream << getDesc() << " ran out of peer ids" << std::endl; - return PEER_ID_INEXISTENT; - } - - // Create a peer - Peer *peer = 0; - peer = new UDPPeer(peer_id_new, sender, this); - - m_peers[peer->id] = peer; - m_peer_ids.push_back(peer->id); - - LOG(dout_con << getDesc() - << "createPeer(): giving peer_id=" << peer_id_new << std::endl); - - { - Buffer reply(4); - writeU8(&reply[0], PACKET_TYPE_CONTROL); - writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID); - writeU16(&reply[2], peer_id_new); - putCommand(ConnectionCommand::createPeer(peer_id_new, reply)); - } - - // Create peer addition event - putEvent(ConnectionEvent::peerAdded(peer_id_new, sender)); - - // We're now talking to a valid peer_id - return peer_id_new; -} - -const std::string Connection::getDesc() -{ - MutexAutoLock _(m_info_mutex); - return std::string("con(")+ - itos(m_udpSocket.GetHandle())+"/"+itos(m_peer_id)+")"; -} - -void Connection::DisconnectPeer(session_t peer_id) -{ - putCommand(ConnectionCommand::disconnect_peer(peer_id)); -} - -void Connection::doResendOne(session_t peer_id) -{ - assert(peer_id != PEER_ID_INEXISTENT); - putCommand(ConnectionCommand::resend_one(peer_id)); -} - -void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum) -{ - assert(channelnum < CHANNEL_COUNT); // Pre-condition - - LOG(dout_con< ack(4); - writeU8(&ack[0], PACKET_TYPE_CONTROL); - writeU8(&ack[1], CONTROLTYPE_ACK); - writeU16(&ack[2], seqnum); - - putCommand(ConnectionCommand::ack(peer_id, channelnum, ack)); - m_sendThread->Trigger(); -} - -UDPPeer* Connection::createServerPeer(const Address &address) -{ - if (ConnectedToServer()) - throw ConnectionException("Already connected to a server"); - - UDPPeer *peer = new UDPPeer(PEER_ID_SERVER, address, this); - peer->SetFullyOpen(); - - { - MutexAutoLock lock(m_peers_mutex); - m_peers[peer->id] = peer; - m_peer_ids.push_back(peer->id); - } - - return peer; -} - -} // namespace diff --git a/src/network/connection.h b/src/network/connection.h index a9e5a18c5..a3665566e 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -1,44 +1,26 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "irrlichttypes.h" -#include "peerhandler.h" #include "socket.h" -#include "constants.h" -#include "util/pointer.h" -#include "util/container.h" -#include "util/numeric.h" -#include "porting.h" -#include "networkprotocol.h" -#include -#include -#include +#include "networkprotocol.h" // session_t class NetworkPacket; +class PeerHandler; namespace con { -class ConnectionReceiveThread; -class ConnectionSendThread; +enum rtt_stat_type { + MIN_RTT, + MAX_RTT, + AVG_RTT, + MIN_JITTER, + MAX_JITTER, + AVG_JITTER +}; enum rate_stat_type { CUR_DL_RATE, @@ -49,295 +31,45 @@ enum rate_stat_type { AVG_LOSS_RATE, }; -class Peer; - -// FIXME: Peer refcounting should generally be replaced by std::shared_ptr -class PeerHelper -{ +class IPeer { public: - PeerHelper() = default; - inline PeerHelper(Peer *peer) { *this = peer; } - ~PeerHelper(); + // Unique id of the peer + const session_t id; - PeerHelper& operator=(Peer *peer); - inline Peer* operator->() const { return m_peer; } - inline Peer* operator&() const { return m_peer; } - - inline bool operator!() { return !m_peer; } - inline bool operator!=(std::nullptr_t) { return !!m_peer; } - -private: - Peer *m_peer = nullptr; -}; - -/* - Connection -*/ - -enum ConnectionEventType { - CONNEVENT_NONE, - CONNEVENT_DATA_RECEIVED, - CONNEVENT_PEER_ADDED, - CONNEVENT_PEER_REMOVED, - CONNEVENT_BIND_FAILED, -}; - -struct ConnectionEvent; -typedef std::shared_ptr ConnectionEventPtr; - -// This is very similar to ConnectionCommand -struct ConnectionEvent -{ - const ConnectionEventType type; - session_t peer_id = 0; - Buffer data; - bool timeout = false; - Address address; - - // We don't want to copy "data" - DISABLE_CLASS_COPY(ConnectionEvent); - - static ConnectionEventPtr create(ConnectionEventType type); - static ConnectionEventPtr dataReceived(session_t peer_id, const Buffer &data); - static ConnectionEventPtr peerAdded(session_t peer_id, Address address); - static ConnectionEventPtr peerRemoved(session_t peer_id, bool is_timeout, Address address); - static ConnectionEventPtr bindFailed(); - - const char *describe() const; - -private: - ConnectionEvent(ConnectionEventType type_) : - type(type_) {} -}; - -struct ConnectionCommand; -typedef std::shared_ptr ConnectionCommandPtr; - -struct BufferedPacket; -typedef std::shared_ptr BufferedPacketPtr; - -class Connection; -class PeerHandler; - -class Peer { - public: - friend class PeerHelper; - - virtual ~Peer() { - MutexAutoLock usage_lock(m_exclusive_access_mutex); - FATAL_ERROR_IF(m_usage != 0, "Reference counting failure"); - } - - // Unique id of the peer - const session_t id; - - void Drop(); - - virtual void PutReliableSendCommand(ConnectionCommandPtr &c, - unsigned int max_packet_size) {}; - - virtual const Address &getAddress() const = 0; - - bool isPendingDeletion() const { - MutexAutoLock lock(m_exclusive_access_mutex); - return m_pending_deletion; - } - void ResetTimeout() { - MutexAutoLock lock(m_exclusive_access_mutex); - m_timeout_counter = 0; - } - - bool isHalfOpen() const { - MutexAutoLock lock(m_exclusive_access_mutex); - return m_half_open; - } - void SetFullyOpen() { - MutexAutoLock lock(m_exclusive_access_mutex); - m_half_open = false; - } - - virtual bool isTimedOut(float timeout, std::string &reason); - - unsigned int m_increment_packets_remaining = 0; - - virtual u16 getNextSplitSequenceNumber(u8 channel) { return 0; }; - virtual void setNextSplitSequenceNumber(u8 channel, u16 seqnum) {}; - virtual SharedBuffer addSplitPacket(u8 channel, BufferedPacketPtr &toadd, - bool reliable) - { - FATAL_ERROR("unimplemented in abstract class"); - } - - virtual bool Ping(float dtime, SharedBuffer& data) { return false; }; - - virtual float getStat(rtt_stat_type type) const { - switch (type) { - case MIN_RTT: - return m_rtt.min_rtt; - case MAX_RTT: - return m_rtt.max_rtt; - case AVG_RTT: - return m_rtt.avg_rtt; - case MIN_JITTER: - return m_rtt.jitter_min; - case MAX_JITTER: - return m_rtt.jitter_max; - case AVG_JITTER: - return m_rtt.jitter_avg; - } - return -1; - } - - protected: - Peer(session_t id, const Address &address, Connection *connection) : - id(id), - m_connection(connection), - address(address), - m_last_timeout_check(porting::getTimeMs()) - { - } - - virtual void reportRTT(float rtt) {}; - - void RTTStatistics(float rtt, - const std::string &profiler_id = "", - unsigned int num_samples = 1000); - - bool IncUseCount(); - void DecUseCount(); - - mutable std::mutex m_exclusive_access_mutex; - - bool m_pending_deletion = false; - - Connection *m_connection; - - // Address of the peer - Address address; - - // Ping timer - float m_ping_timer = 0.0f; - - private: - struct rttstats { - float jitter_min = FLT_MAX; - float jitter_max = 0.0f; - float jitter_avg = -1.0f; - float min_rtt = FLT_MAX; - float max_rtt = 0.0f; - float avg_rtt = -1.0f; - }; - - rttstats m_rtt; - float m_last_rtt = -1.0f; - - /* - Until the peer has communicated with us using their assigned peer id - the connection is considered half-open. - During this time we inhibit re-sending any reliables or pings. This - is to avoid spending too many resources on a potential DoS attack - and to make sure Minetest servers are not useful for UDP amplificiation. - */ - bool m_half_open = true; - - // current usage count - unsigned int m_usage = 0; - - // Seconds from last receive - float m_timeout_counter = 0.0f; - - u64 m_last_timeout_check; -}; - -class UDPPeer; - -class Connection -{ -public: - friend class ConnectionSendThread; - friend class ConnectionReceiveThread; - - Connection(u32 protocol_id, u32 max_packet_size, float timeout, bool ipv6, - PeerHandler *peerhandler); - ~Connection(); - - /* Interface */ - ConnectionEventPtr waitEvent(u32 timeout_ms); - - void putCommand(ConnectionCommandPtr c); - - void SetTimeoutMs(u32 timeout) { m_bc_receive_timeout = timeout; } - void Serve(Address bind_addr); - void Connect(Address address); - bool Connected(); - void Disconnect(); - bool ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms); - void Receive(NetworkPacket *pkt); - bool TryReceive(NetworkPacket *pkt); - void Send(session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable); - session_t GetPeerID() const { return m_peer_id; } - Address GetPeerAddress(session_t peer_id); - float getPeerStat(session_t peer_id, rtt_stat_type type); - float getLocalStat(rate_stat_type type); - u32 GetProtocolID() const { return m_protocol_id; }; - const std::string getDesc(); - void DisconnectPeer(session_t peer_id); + virtual const Address &getAddress() const = 0; protected: - PeerHelper getPeerNoEx(session_t peer_id); - session_t lookupPeer(const Address& sender); - - session_t createPeer(const Address& sender, int fd); - UDPPeer* createServerPeer(const Address& sender); - bool deletePeer(session_t peer_id, bool timeout); - - void SetPeerID(session_t id) { m_peer_id = id; } - - void doResendOne(session_t peer_id); - - void sendAck(session_t peer_id, u8 channelnum, u16 seqnum); - - std::vector getPeerIDs() - { - MutexAutoLock peerlock(m_peers_mutex); - return m_peer_ids; - } - - u32 getActiveCount(); - - UDPSocket m_udpSocket; - // Command queue: user -> SendThread - MutexedQueue m_command_queue; - - void putEvent(ConnectionEventPtr e); - - void TriggerSend(); - - bool ConnectedToServer() - { - return getPeerNoEx(PEER_ID_SERVER) != nullptr; - } -private: - // Event queue: ReceiveThread -> user - MutexedQueue m_event_queue; - - session_t m_peer_id = 0; - u32 m_protocol_id; - - std::map m_peers; - std::vector m_peer_ids; - std::mutex m_peers_mutex; - - std::unique_ptr m_sendThread; - std::unique_ptr m_receiveThread; - - mutable std::mutex m_info_mutex; - - // Backwards compatibility - PeerHandler *m_bc_peerhandler; - u32 m_bc_receive_timeout = 0; - - bool m_shutting_down = false; + IPeer(session_t id) : id(id) {} + ~IPeer() {} }; +class IConnection +{ +public: + virtual ~IConnection() = default; + + virtual void SetTimeoutMs(u32 timeout) = 0; + virtual void Serve(Address bind_addr) = 0; + virtual void Connect(Address address) = 0; + virtual bool Connected() = 0; + virtual void Disconnect() = 0; + virtual void DisconnectPeer(session_t peer_id) = 0; + + virtual bool ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms) = 0; + virtual void Receive(NetworkPacket *pkt) = 0; + bool TryReceive(NetworkPacket *pkt) { + return ReceiveTimeoutMs(pkt, 0); + } + + virtual void Send(session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable) = 0; + + virtual session_t GetPeerID() const = 0; + virtual Address GetPeerAddress(session_t peer_id) = 0; + virtual float getPeerStat(session_t peer_id, rtt_stat_type type) = 0; + virtual float getLocalStat(rate_stat_type type) = 0; +}; + +// MTP = Minetest Protocol +IConnection *createMTP(float timeout, bool ipv6, PeerHandler *handler); + } // namespace diff --git a/src/network/mtp/impl.cpp b/src/network/mtp/impl.cpp new file mode 100644 index 000000000..2d4a99119 --- /dev/null +++ b/src/network/mtp/impl.cpp @@ -0,0 +1,1667 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include +#include +#include +#include +#include "network/mtp/internal.h" +#include "serialization.h" +#include "log.h" +#include "porting.h" +#include "network/mtp/threads.h" +#include "network/peerhandler.h" +#include "network/networkpacket.h" +#include "util/serialize.h" +#include "util/numeric.h" +#include "util/string.h" +#include "settings.h" +#include "profiler.h" + +namespace con +{ + +/******************************************************************************/ +/* defines used for debugging and profiling */ +/******************************************************************************/ +#ifdef NDEBUG + #define PROFILE(a) +#else + #define PROFILE(a) a +#endif + +// TODO: Clean this up. +#define LOG(a) a + +#define PING_TIMEOUT 5.0f + +// exponent base +#define RESEND_SCALE_BASE 1.5f + +// since spacing is exponential the numbers here shouldn't be too high +// (it's okay to start out quick) +#define RESEND_TIMEOUT_MIN 0.1f +#define RESEND_TIMEOUT_MAX 2.0f +#define RESEND_TIMEOUT_FACTOR 2 + +u16 BufferedPacket::getSeqnum() const +{ + if (size() < BASE_HEADER_SIZE + 3) + return 0; // should never happen + + return readU16(&data[BASE_HEADER_SIZE + 1]); +} + +BufferedPacketPtr makePacket(const Address &address, const SharedBuffer &data, + u32 protocol_id, session_t sender_peer_id, u8 channel) +{ + u32 packet_size = data.getSize() + BASE_HEADER_SIZE; + + auto p = std::make_shared(packet_size); + p->address = address; + + writeU32(&p->data[0], protocol_id); + writeU16(&p->data[4], sender_peer_id); + writeU8(&p->data[6], channel); + + memcpy(&p->data[BASE_HEADER_SIZE], *data, data.getSize()); + + return p; +} + +SharedBuffer makeOriginalPacket(const SharedBuffer &data) +{ + u32 header_size = 1; + u32 packet_size = data.getSize() + header_size; + SharedBuffer b(packet_size); + + writeU8(&(b[0]), PACKET_TYPE_ORIGINAL); + if (data.getSize() > 0) { + memcpy(&(b[header_size]), *data, data.getSize()); + } + return b; +} + +// Split data in chunks and add TYPE_SPLIT headers to them +void makeSplitPacket(const SharedBuffer &data, u32 chunksize_max, u16 seqnum, + std::list> *chunks) +{ + // Chunk packets, containing the TYPE_SPLIT header + const u32 chunk_header_size = 7; + const u32 maximum_data_size = chunksize_max - chunk_header_size; + u32 start = 0, end = 0; + u16 chunk_num = 0; + do { + end = start + maximum_data_size - 1; + if (end > data.getSize() - 1) + end = data.getSize() - 1; + + u32 payload_size = end - start + 1; + u32 packet_size = chunk_header_size + payload_size; + + SharedBuffer chunk(packet_size); + + writeU8(&chunk[0], PACKET_TYPE_SPLIT); + writeU16(&chunk[1], seqnum); + // [3] u16 chunk_count is written at next stage + writeU16(&chunk[5], chunk_num); + memcpy(&chunk[chunk_header_size], &data[start], payload_size); + + chunks->push_back(chunk); + + start = end + 1; + sanity_check(chunk_num < 0xFFFF); // overflow + chunk_num++; + } + while (end != data.getSize() - 1); + + for (auto &chunk : *chunks) { + // Write chunk_count + writeU16(&chunk[3], chunk_num); + } +} + +void makeAutoSplitPacket(const SharedBuffer &data, u32 chunksize_max, + u16 &split_seqnum, std::list> *list) +{ + u32 original_header_size = 1; + + if (data.getSize() + original_header_size > chunksize_max) { + makeSplitPacket(data, chunksize_max, split_seqnum, list); + split_seqnum++; + return; + } + + list->push_back(makeOriginalPacket(data)); +} + +SharedBuffer makeReliablePacket(const SharedBuffer &data, u16 seqnum) +{ + u32 header_size = 3; + u32 packet_size = data.getSize() + header_size; + SharedBuffer b(packet_size); + + writeU8(&b[0], PACKET_TYPE_RELIABLE); + writeU16(&b[1], seqnum); + + memcpy(&b[header_size], *data, data.getSize()); + + return b; +} + +/* + ReliablePacketBuffer +*/ + +void ReliablePacketBuffer::print() +{ + MutexAutoLock listlock(m_list_mutex); + LOG(dout_con<<"Dump of ReliablePacketBuffer:" << std::endl); + unsigned int index = 0; + for (BufferedPacketPtr &packet : m_list) { + LOG(dout_con<getSeqnum() << std::endl); + index++; + } +} + +bool ReliablePacketBuffer::empty() +{ + MutexAutoLock listlock(m_list_mutex); + return m_list.empty(); +} + +u32 ReliablePacketBuffer::size() +{ + MutexAutoLock listlock(m_list_mutex); + return m_list.size(); +} + +ReliablePacketBuffer::FindResult ReliablePacketBuffer::findPacketNoLock(u16 seqnum) +{ + for (auto it = m_list.begin(); it != m_list.end(); ++it) { + if ((*it)->getSeqnum() == seqnum) + return it; + } + return m_list.end(); +} + +bool ReliablePacketBuffer::getFirstSeqnum(u16& result) +{ + MutexAutoLock listlock(m_list_mutex); + if (m_list.empty()) + return false; + result = m_list.front()->getSeqnum(); + return true; +} + +BufferedPacketPtr ReliablePacketBuffer::popFirst() +{ + MutexAutoLock listlock(m_list_mutex); + if (m_list.empty()) + throw NotFoundException("Buffer is empty"); + + BufferedPacketPtr p(m_list.front()); + m_list.pop_front(); + + if (m_list.empty()) { + m_oldest_non_answered_ack = 0; + } else { + m_oldest_non_answered_ack = m_list.front()->getSeqnum(); + } + return p; +} + +BufferedPacketPtr ReliablePacketBuffer::popSeqnum(u16 seqnum) +{ + MutexAutoLock listlock(m_list_mutex); + auto r = findPacketNoLock(seqnum); + if (r == m_list.end()) { + LOG(dout_con<<"Sequence number: " << seqnum + << " not found in reliable buffer"<getSeqnum(); + } + return p; +} + +void ReliablePacketBuffer::insert(BufferedPacketPtr &p_ptr, u16 next_expected) +{ + MutexAutoLock listlock(m_list_mutex); + const BufferedPacket &p = *p_ptr; + + if (p.size() < BASE_HEADER_SIZE + 3) { + errorstream << "ReliablePacketBuffer::insert(): Invalid data size for " + "reliable packet" << std::endl; + return; + } + u8 type = readU8(&p.data[BASE_HEADER_SIZE + 0]); + if (type != PACKET_TYPE_RELIABLE) { + errorstream << "ReliablePacketBuffer::insert(): type is not reliable" + << std::endl; + return; + } + const u16 seqnum = p.getSeqnum(); + + if (!seqnum_in_window(seqnum, next_expected, MAX_RELIABLE_WINDOW_SIZE)) { + errorstream << "ReliablePacketBuffer::insert(): seqnum is outside of " + "expected window " << std::endl; + return; + } + if (seqnum == next_expected) { + errorstream << "ReliablePacketBuffer::insert(): seqnum is next expected" + << std::endl; + return; + } + + sanity_check(m_list.size() <= SEQNUM_MAX); // FIXME: Handle the error? + + // Find the right place for the packet and insert it there + // If list is empty, just add it + if (m_list.empty()) { + m_list.push_back(p_ptr); + m_oldest_non_answered_ack = seqnum; + // Done. + return; + } + + // Otherwise find the right place + auto it = m_list.begin(); + // Find the first packet in the list which has a higher seqnum + u16 s = (*it)->getSeqnum(); + + /* case seqnum is smaller then next_expected seqnum */ + /* this is true e.g. on wrap around */ + if (seqnum < next_expected) { + while(((s < seqnum) || (s >= next_expected)) && (it != m_list.end())) { + ++it; + if (it != m_list.end()) + s = (*it)->getSeqnum(); + } + } + /* non wrap around case (at least for incoming and next_expected */ + else + { + while(((s < seqnum) && (s >= next_expected)) && (it != m_list.end())) { + ++it; + if (it != m_list.end()) + s = (*it)->getSeqnum(); + } + } + + if (s == seqnum) { + /* nothing to do this seems to be a resent packet */ + /* for paranoia reason data should be compared */ + auto &i = *it; + if ( + (i->getSeqnum() != seqnum) || + (i->size() != p.size()) || + (i->address != p.address) + ) + { + /* if this happens your maximum transfer window may be to big */ + char buf[200]; + snprintf(buf, sizeof(buf), + "Duplicated seqnum %d non matching packet detected:\n", + seqnum); + warningstream << buf; + snprintf(buf, sizeof(buf), + "Old: seqnum: %05d size: %04zu, address: %s\n", + i->getSeqnum(), i->size(), + i->address.serializeString().c_str()); + warningstream << buf; + snprintf(buf, sizeof(buf), + "New: seqnum: %05d size: %04zu, address: %s\n", + p.getSeqnum(), p.size(), + p.address.serializeString().c_str()); + warningstream << buf << std::flush; + throw IncomingDataCorruption("duplicated packet isn't same as original one"); + } + } + /* insert or push back */ + else if (it != m_list.end()) { + m_list.insert(it, p_ptr); + } else { + m_list.push_back(p_ptr); + } + + /* update last packet number */ + m_oldest_non_answered_ack = m_list.front()->getSeqnum(); +} + +void ReliablePacketBuffer::incrementTimeouts(float dtime) +{ + MutexAutoLock listlock(m_list_mutex); + for (auto &packet : m_list) { + packet->time += dtime; + packet->totaltime += dtime; + } +} + +u32 ReliablePacketBuffer::getTimedOuts(float timeout) +{ + MutexAutoLock listlock(m_list_mutex); + u32 count = 0; + for (auto &packet : m_list) { + if (packet->totaltime >= timeout) + count++; + } + return count; +} + +std::vector> + ReliablePacketBuffer::getResend(float timeout, u32 max_packets) +{ + MutexAutoLock listlock(m_list_mutex); + std::vector> timed_outs; + for (auto &packet : m_list) { + // resend time scales exponentially with each cycle + const float pkt_timeout = timeout * powf(RESEND_SCALE_BASE, packet->resend_count); + + if (packet->time < pkt_timeout) + continue; + + // caller will resend packet so reset time and increase counter + packet->time = 0.0f; + packet->resend_count++; + + timed_outs.emplace_back(packet); + + if (timed_outs.size() >= max_packets) + break; + } + return timed_outs; +} + +/* + IncomingSplitPacket +*/ + +bool IncomingSplitPacket::insert(u32 chunk_num, SharedBuffer &chunkdata) +{ + sanity_check(chunk_num < chunk_count); + + // If chunk already exists, ignore it. + // Sometimes two identical packets may arrive when there is network + // lag and the server re-sends stuff. + if (chunks.find(chunk_num) != chunks.end()) + return false; + + // Set chunk data in buffer + chunks[chunk_num] = chunkdata; + + return true; +} + +SharedBuffer IncomingSplitPacket::reassemble() +{ + sanity_check(allReceived()); + + // Calculate total size + u32 totalsize = 0; + for (const auto &chunk : chunks) + totalsize += chunk.second.getSize(); + + SharedBuffer fulldata(totalsize); + + // Copy chunks to data buffer + u32 start = 0; + for (u32 chunk_i = 0; chunk_i < chunk_count; chunk_i++) { + const SharedBuffer &buf = chunks[chunk_i]; + memcpy(&fulldata[start], *buf, buf.getSize()); + start += buf.getSize(); + } + + return fulldata; +} + +/* + IncomingSplitBuffer +*/ + +IncomingSplitBuffer::~IncomingSplitBuffer() +{ + MutexAutoLock listlock(m_map_mutex); + for (auto &i : m_buf) { + delete i.second; + } +} + +SharedBuffer IncomingSplitBuffer::insert(BufferedPacketPtr &p_ptr, bool reliable) +{ + MutexAutoLock listlock(m_map_mutex); + const BufferedPacket &p = *p_ptr; + + u32 headersize = BASE_HEADER_SIZE + 7; + if (p.size() < headersize) { + errorstream << "Invalid data size for split packet" << std::endl; + return SharedBuffer(); + } + u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]); + u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); + u16 chunk_count = readU16(&p.data[BASE_HEADER_SIZE+3]); + u16 chunk_num = readU16(&p.data[BASE_HEADER_SIZE+5]); + + if (type != PACKET_TYPE_SPLIT) { + errorstream << "IncomingSplitBuffer::insert(): type is not split" + << std::endl; + return SharedBuffer(); + } + if (chunk_num >= chunk_count) { + errorstream << "IncomingSplitBuffer::insert(): chunk_num=" << chunk_num + << " >= chunk_count=" << chunk_count << std::endl; + return SharedBuffer(); + } + + // Add if doesn't exist + IncomingSplitPacket *sp; + if (m_buf.find(seqnum) == m_buf.end()) { + sp = new IncomingSplitPacket(chunk_count, reliable); + m_buf[seqnum] = sp; + } else { + sp = m_buf[seqnum]; + } + + if (chunk_count != sp->chunk_count) { + errorstream << "IncomingSplitBuffer::insert(): chunk_count=" + << chunk_count << " != sp->chunk_count=" << sp->chunk_count + << std::endl; + return SharedBuffer(); + } + if (reliable != sp->reliable) + LOG(derr_con<<"Connection: WARNING: reliable="<reliable="<reliable + < chunkdata(chunkdatasize); + memcpy(*chunkdata, &(p.data[headersize]), chunkdatasize); + + if (!sp->insert(chunk_num, chunkdata)) + return SharedBuffer(); + + // If not all chunks are received, return empty buffer + if (!sp->allReceived()) + return SharedBuffer(); + + SharedBuffer fulldata = sp->reassemble(); + + // Remove sp from buffer + m_buf.erase(seqnum); + delete sp; + + return fulldata; +} + +void IncomingSplitBuffer::removeUnreliableTimedOuts(float dtime, float timeout) +{ + MutexAutoLock listlock(m_map_mutex); + std::vector remove_queue; + { + for (const auto &i : m_buf) { + IncomingSplitPacket *p = i.second; + // Reliable ones are not removed by timeout + if (p->reliable) + continue; + p->time += dtime; + if (p->time >= timeout) + remove_queue.push_back(i.first); + } + } + for (u16 j : remove_queue) { + LOG(dout_con<<"NOTE: Removing timed out unreliable split packet"<second; + m_buf.erase(it); + } +} + +/* + ConnectionCommand + */ + +ConnectionCommandPtr ConnectionCommand::create(ConnectionCommandType type) +{ + return ConnectionCommandPtr(new ConnectionCommand(type)); +} + +ConnectionCommandPtr ConnectionCommand::serve(Address address) +{ + auto c = create(CONNCMD_SERVE); + c->address = address; + return c; +} + +ConnectionCommandPtr ConnectionCommand::connect(Address address) +{ + auto c = create(CONNCMD_CONNECT); + c->address = address; + return c; +} + +ConnectionCommandPtr ConnectionCommand::disconnect() +{ + return create(CONNCMD_DISCONNECT); +} + +ConnectionCommandPtr ConnectionCommand::disconnect_peer(session_t peer_id) +{ + auto c = create(CONNCMD_DISCONNECT_PEER); + c->peer_id = peer_id; + return c; +} + +ConnectionCommandPtr ConnectionCommand::resend_one(session_t peer_id) +{ + auto c = create(CONNCMD_RESEND_ONE); + c->peer_id = peer_id; + c->channelnum = 0; // must be same as createPeer + c->reliable = true; + return c; +} + +ConnectionCommandPtr ConnectionCommand::send(session_t peer_id, u8 channelnum, + NetworkPacket *pkt, bool reliable) +{ + auto c = create(CONNCMD_SEND); + c->peer_id = peer_id; + c->channelnum = channelnum; + c->reliable = reliable; + c->data = pkt->oldForgePacket(); + return c; +} + +ConnectionCommandPtr ConnectionCommand::ack(session_t peer_id, u8 channelnum, const Buffer &data) +{ + auto c = create(CONCMD_ACK); + c->peer_id = peer_id; + c->channelnum = channelnum; + c->reliable = false; + data.copyTo(c->data); + return c; +} + +ConnectionCommandPtr ConnectionCommand::createPeer(session_t peer_id, const Buffer &data) +{ + auto c = create(CONCMD_CREATE_PEER); + c->peer_id = peer_id; + c->channelnum = 0; + c->reliable = true; + c->raw = true; + data.copyTo(c->data); + return c; +} + +/* + Channel +*/ + +u16 Channel::readNextIncomingSeqNum() +{ + MutexAutoLock internal(m_internal_mutex); + return next_incoming_seqnum; +} + +u16 Channel::incNextIncomingSeqNum() +{ + MutexAutoLock internal(m_internal_mutex); + u16 retval = next_incoming_seqnum; + next_incoming_seqnum++; + return retval; +} + +u16 Channel::readNextSplitSeqNum() +{ + MutexAutoLock internal(m_internal_mutex); + return next_outgoing_split_seqnum; +} +void Channel::setNextSplitSeqNum(u16 seqnum) +{ + MutexAutoLock internal(m_internal_mutex); + next_outgoing_split_seqnum = seqnum; +} + +u16 Channel::getOutgoingSequenceNumber(bool& successful) +{ + MutexAutoLock internal(m_internal_mutex); + + u16 retval = next_outgoing_seqnum; + successful = false; + + /* shortcut if there ain't any packet in outgoing list */ + if (outgoing_reliables_sent.empty()) { + successful = true; + next_outgoing_seqnum++; + return retval; + } + + u16 lowest_unacked_seqnumber; + if (outgoing_reliables_sent.getFirstSeqnum(lowest_unacked_seqnumber)) { + if (lowest_unacked_seqnumber < next_outgoing_seqnum) { + // ugly cast but this one is required in order to tell compiler we + // know about difference of two unsigned may be negative in general + // but we already made sure it won't happen in this case + if (((u16)(next_outgoing_seqnum - lowest_unacked_seqnumber)) > m_window_size) { + return 0; + } + } else { + // ugly cast but this one is required in order to tell compiler we + // know about difference of two unsigned may be negative in general + // but we already made sure it won't happen in this case + if ((next_outgoing_seqnum + (u16)(SEQNUM_MAX - lowest_unacked_seqnumber)) > + m_window_size) { + return 0; + } + } + } + + successful = true; + next_outgoing_seqnum++; + return retval; +} + +u16 Channel::readOutgoingSequenceNumber() +{ + MutexAutoLock internal(m_internal_mutex); + return next_outgoing_seqnum; +} + +bool Channel::putBackSequenceNumber(u16 seqnum) +{ + if (((seqnum + 1) % (SEQNUM_MAX+1)) == next_outgoing_seqnum) { + + next_outgoing_seqnum = seqnum; + return true; + } + return false; +} + +void Channel::UpdateBytesSent(unsigned int bytes, unsigned int packets) +{ + MutexAutoLock internal(m_internal_mutex); + current_bytes_transfered += bytes; + current_packet_successful += packets; +} + +void Channel::UpdateBytesReceived(unsigned int bytes) { + MutexAutoLock internal(m_internal_mutex); + current_bytes_received += bytes; +} + +void Channel::UpdateBytesLost(unsigned int bytes) +{ + MutexAutoLock internal(m_internal_mutex); + current_bytes_lost += bytes; +} + + +void Channel::UpdatePacketLossCounter(unsigned int count) +{ + MutexAutoLock internal(m_internal_mutex); + current_packet_loss += count; +} + +void Channel::UpdatePacketTooLateCounter() +{ + MutexAutoLock internal(m_internal_mutex); + current_packet_too_late++; +} + +void Channel::UpdateTimers(float dtime) +{ + bpm_counter += dtime; + packet_loss_counter += dtime; + + if (packet_loss_counter > 1.0f) { + packet_loss_counter -= 1.0f; + + unsigned int packet_loss = 11; /* use a neutral value for initialization */ + unsigned int packets_successful = 0; + //unsigned int packet_too_late = 0; + + bool reasonable_amount_of_data_transmitted = false; + + { + MutexAutoLock internal(m_internal_mutex); + packet_loss = current_packet_loss; + //packet_too_late = current_packet_too_late; + packets_successful = current_packet_successful; + + if (current_bytes_transfered > (unsigned int) (m_window_size*512/2)) { + reasonable_amount_of_data_transmitted = true; + } + current_packet_loss = 0; + current_packet_too_late = 0; + current_packet_successful = 0; + } + + /* dynamic window size */ + float successful_to_lost_ratio = 0.0f; + bool done = false; + + if (packets_successful > 0) { + successful_to_lost_ratio = packet_loss/packets_successful; + } else if (packet_loss > 0) { + setWindowSize(m_window_size - 10); + done = true; + } + + if (!done) { + if (successful_to_lost_ratio < 0.01f) { + /* don't even think about increasing if we didn't even + * use major parts of our window */ + if (reasonable_amount_of_data_transmitted) + setWindowSize(m_window_size + 100); + } else if (successful_to_lost_ratio < 0.05f) { + /* don't even think about increasing if we didn't even + * use major parts of our window */ + if (reasonable_amount_of_data_transmitted) + setWindowSize(m_window_size + 50); + } else if (successful_to_lost_ratio > 0.15f) { + setWindowSize(m_window_size - 100); + } else if (successful_to_lost_ratio > 0.1f) { + setWindowSize(m_window_size - 50); + } + } + } + + if (bpm_counter > 10.0f) { + { + MutexAutoLock internal(m_internal_mutex); + cur_kbps = + (((float) current_bytes_transfered)/bpm_counter)/1024.0f; + current_bytes_transfered = 0; + cur_kbps_lost = + (((float) current_bytes_lost)/bpm_counter)/1024.0f; + current_bytes_lost = 0; + cur_incoming_kbps = + (((float) current_bytes_received)/bpm_counter)/1024.0f; + current_bytes_received = 0; + bpm_counter = 0.0f; + } + + if (cur_kbps > max_kbps) { + max_kbps = cur_kbps; + } + + if (cur_kbps_lost > max_kbps_lost) { + max_kbps_lost = cur_kbps_lost; + } + + if (cur_incoming_kbps > max_incoming_kbps) { + max_incoming_kbps = cur_incoming_kbps; + } + + rate_samples = MYMIN(rate_samples+1,10); + float old_fraction = ((float) (rate_samples-1) )/( (float) rate_samples); + avg_kbps = avg_kbps * old_fraction + + cur_kbps * (1.0 - old_fraction); + avg_kbps_lost = avg_kbps_lost * old_fraction + + cur_kbps_lost * (1.0 - old_fraction); + avg_incoming_kbps = avg_incoming_kbps * old_fraction + + cur_incoming_kbps * (1.0 - old_fraction); + } +} + + +/* + Peer +*/ + +PeerHelper::~PeerHelper() +{ + if (m_peer) + m_peer->DecUseCount(); + + m_peer = nullptr; +} + +PeerHelper& PeerHelper::operator=(Peer* peer) +{ + if (m_peer) + m_peer->DecUseCount(); + m_peer = peer; + if (peer && !peer->IncUseCount()) + m_peer = nullptr; + return *this; +} + +bool Peer::IncUseCount() +{ + MutexAutoLock lock(m_exclusive_access_mutex); + + if (!m_pending_deletion) { + this->m_usage++; + return true; + } + + return false; +} + +void Peer::DecUseCount() +{ + { + MutexAutoLock lock(m_exclusive_access_mutex); + sanity_check(m_usage > 0); + m_usage--; + + if (!((m_pending_deletion) && (m_usage == 0))) + return; + } + delete this; +} + +void Peer::RTTStatistics(float rtt, const std::string &profiler_id, + unsigned int num_samples) { + + if (m_last_rtt > 0) { + /* set min max values */ + if (rtt < m_rtt.min_rtt) + m_rtt.min_rtt = rtt; + if (rtt >= m_rtt.max_rtt) + m_rtt.max_rtt = rtt; + + /* do average calculation */ + if (m_rtt.avg_rtt < 0.0) + m_rtt.avg_rtt = rtt; + else + m_rtt.avg_rtt = m_rtt.avg_rtt * (num_samples/(num_samples-1)) + + rtt * (1/num_samples); + + /* do jitter calculation */ + + //just use some neutral value at beginning + float jitter = m_rtt.jitter_min; + + if (rtt > m_last_rtt) + jitter = rtt-m_last_rtt; + + if (rtt <= m_last_rtt) + jitter = m_last_rtt - rtt; + + if (jitter < m_rtt.jitter_min) + m_rtt.jitter_min = jitter; + if (jitter >= m_rtt.jitter_max) + m_rtt.jitter_max = jitter; + + if (m_rtt.jitter_avg < 0.0) + m_rtt.jitter_avg = jitter; + else + m_rtt.jitter_avg = m_rtt.jitter_avg * (num_samples/(num_samples-1)) + + jitter * (1/num_samples); + + if (!profiler_id.empty()) { + g_profiler->graphAdd(profiler_id + " RTT [ms]", rtt * 1000.f); + g_profiler->graphAdd(profiler_id + " jitter [ms]", jitter * 1000.f); + } + } + /* save values required for next loop */ + m_last_rtt = rtt; +} + +bool Peer::isTimedOut(float timeout, std::string &reason) +{ + MutexAutoLock lock(m_exclusive_access_mutex); + + { + u64 current_time = porting::getTimeMs(); + float dtime = CALC_DTIME(m_last_timeout_check, current_time); + m_last_timeout_check = current_time; + m_timeout_counter += dtime; + } + if (m_timeout_counter > timeout) { + reason = "timeout counter"; + return true; + } + + return false; +} + +void Peer::Drop() +{ + { + MutexAutoLock usage_lock(m_exclusive_access_mutex); + m_pending_deletion = true; + if (m_usage != 0) + return; + } + + PROFILE(std::stringstream peerIdentifier1); + PROFILE(peerIdentifier1 << "runTimeouts[" << m_connection->getDesc() + << ";" << id << ";RELIABLE]"); + PROFILE(g_profiler->remove(peerIdentifier1.str())); + PROFILE(std::stringstream peerIdentifier2); + PROFILE(peerIdentifier2 << "sendPackets[" << m_connection->getDesc() + << ";" << id << ";RELIABLE]"); + PROFILE(ScopeProfiler peerprofiler(g_profiler, peerIdentifier2.str(), SPT_AVG)); + + delete this; +} + +UDPPeer::UDPPeer(session_t id, const Address &address, Connection *connection) : + Peer(id, address, connection) +{ + for (Channel &channel : channels) + channel.setWindowSize(START_RELIABLE_WINDOW_SIZE); +} + +bool UDPPeer::isTimedOut(float timeout, std::string &reason) +{ + if (Peer::isTimedOut(timeout, reason)) + return true; + + MutexAutoLock lock(m_exclusive_access_mutex); + + for (int i = 0; i < CHANNEL_COUNT; i++) { + Channel &channel = channels[i]; + if (channel.outgoing_reliables_sent.getTimedOuts(timeout) > 0) { + reason = "outgoing reliables channel=" + itos(i); + return true; + } + } + + return false; +} + +void UDPPeer::reportRTT(float rtt) +{ + if (rtt < 0.0) { + return; + } + RTTStatistics(rtt,"rudp",MAX_RELIABLE_WINDOW_SIZE*10); + + // use this value to decide the resend timeout + float timeout = getStat(AVG_RTT) * RESEND_TIMEOUT_FACTOR; + if (timeout < RESEND_TIMEOUT_MIN) + timeout = RESEND_TIMEOUT_MIN; + if (timeout > RESEND_TIMEOUT_MAX) + timeout = RESEND_TIMEOUT_MAX; + + setResendTimeout(timeout); +} + +bool UDPPeer::Ping(float dtime,SharedBuffer& data) +{ + m_ping_timer += dtime; + if (!isHalfOpen() && m_ping_timer >= PING_TIMEOUT) + { + // Create and send PING packet + writeU8(&data[0], PACKET_TYPE_CONTROL); + writeU8(&data[1], CONTROLTYPE_PING); + m_ping_timer = 0.0f; + return true; + } + return false; +} + +void UDPPeer::PutReliableSendCommand(ConnectionCommandPtr &c, + unsigned int max_packet_size) +{ + if (m_pending_disconnect) + return; + + Channel &chan = channels[c->channelnum]; + + if (chan.queued_commands.empty() && + /* don't queue more packets then window size */ + (chan.queued_reliables.size() + 1 < chan.getWindowSize() / 2)) { + LOG(dout_con<getDesc() + <<" processing reliable command for peer id: " << c->peer_id + <<" data size: " << c->data.getSize() << std::endl); + if (processReliableSendCommand(c, max_packet_size)) + return; + } else { + LOG(dout_con<getDesc() + <<" Queueing reliable command for peer id: " << c->peer_id + <<" data size: " << c->data.getSize() <= chan.getWindowSize() / 2) { + LOG(derr_con << m_connection->getDesc() + << "Possible packet stall to peer id: " << c->peer_id + << " queued_commands=" << chan.queued_commands.size() + << std::endl); + } + } + chan.queued_commands.push_back(c); +} + +bool UDPPeer::processReliableSendCommand( + ConnectionCommandPtr &c_ptr, + unsigned int max_packet_size) +{ + if (m_pending_disconnect) + return true; + + const auto &c = *c_ptr; + Channel &chan = channels[c.channelnum]; + + const u32 chunksize_max = max_packet_size + - BASE_HEADER_SIZE + - RELIABLE_HEADER_SIZE; + + std::list> originals; + + if (c.raw) { + originals.emplace_back(c.data); + } else { + u16 split_seqnum = chan.readNextSplitSeqNum(); + makeAutoSplitPacket(c.data, chunksize_max, split_seqnum, &originals); + chan.setNextSplitSeqNum(split_seqnum); + } + + sanity_check(originals.size() < MAX_RELIABLE_WINDOW_SIZE); + + bool have_sequence_number = false; + bool have_initial_sequence_number = false; + std::queue toadd; + u16 initial_sequence_number = 0; + + for (SharedBuffer &original : originals) { + u16 seqnum = chan.getOutgoingSequenceNumber(have_sequence_number); + + /* oops, we don't have enough sequence numbers to send this packet */ + if (!have_sequence_number) + break; + + if (!have_initial_sequence_number) + { + initial_sequence_number = seqnum; + have_initial_sequence_number = true; + } + + SharedBuffer reliable = makeReliablePacket(original, seqnum); + + // Add base headers and make a packet + BufferedPacketPtr p = con::makePacket(address, reliable, + m_connection->GetProtocolID(), m_connection->GetPeerID(), + c.channelnum); + + toadd.push(p); + } + + if (have_sequence_number) { + while (!toadd.empty()) { + BufferedPacketPtr p = toadd.front(); + toadd.pop(); +// LOG(dout_con<getDesc() +// << " queuing reliable packet for peer_id: " << c.peer_id +// << " channel: " << (c.channelnum&0xFF) +// << " seqnum: " << readU16(&p.data[BASE_HEADER_SIZE+1]) +// << std::endl) + chan.queued_reliables.push(p); + } + sanity_check(chan.queued_reliables.size() < 0xFFFF); + return true; + } + + u16 packets_available = toadd.size(); + /* we didn't get a single sequence number no need to fill queue */ + if (!have_initial_sequence_number) { + LOG(derr_con << m_connection->getDesc() << "Ran out of sequence numbers!" << std::endl); + return false; + } + + while (!toadd.empty()) { + /* remove packet */ + toadd.pop(); + + bool successfully_put_back_sequence_number + = chan.putBackSequenceNumber( + (initial_sequence_number+toadd.size() % (SEQNUM_MAX+1))); + + FATAL_ERROR_IF(!successfully_put_back_sequence_number, "error"); + } + + u32 n_queued = chan.outgoing_reliables_sent.size(); + + LOG(dout_con<getDesc() + << " Windowsize exceeded on reliable sending " + << c.data.getSize() << " bytes" + << std::endl << "\t\tinitial_sequence_number: " + << initial_sequence_number + << std::endl << "\t\tgot at most : " + << packets_available << " packets" + << std::endl << "\t\tpackets queued : " + << n_queued + << std::endl); + + return false; +} + +void UDPPeer::RunCommandQueues( + unsigned int max_packet_size, + unsigned int maxtransfer) +{ + + for (Channel &channel : channels) { + + if ((!channel.queued_commands.empty()) && + (channel.queued_reliables.size() < maxtransfer)) { + try { + ConnectionCommandPtr c = channel.queued_commands.front(); + + LOG(dout_con << m_connection->getDesc() + << " processing queued reliable command " << std::endl); + + // Packet is processed, remove it from queue + if (processReliableSendCommand(c, max_packet_size)) { + channel.queued_commands.pop_front(); + } else { + LOG(dout_con << m_connection->getDesc() + << " Failed to queue packets for peer_id: " << c->peer_id + << ", delaying sending of " << c->data.getSize() + << " bytes" << std::endl); + } + } + catch (ItemNotFoundException &e) { + // intentionally empty + } + } + } +} + +u16 UDPPeer::getNextSplitSequenceNumber(u8 channel) +{ + assert(channel < CHANNEL_COUNT); // Pre-condition + return channels[channel].readNextSplitSeqNum(); +} + +void UDPPeer::setNextSplitSequenceNumber(u8 channel, u16 seqnum) +{ + assert(channel < CHANNEL_COUNT); // Pre-condition + channels[channel].setNextSplitSeqNum(seqnum); +} + +SharedBuffer UDPPeer::addSplitPacket(u8 channel, BufferedPacketPtr &toadd, + bool reliable) +{ + assert(channel < CHANNEL_COUNT); // Pre-condition + return channels[channel].incoming_splits.insert(toadd, reliable); +} + +/* + ConnectionEvent +*/ + +const char *ConnectionEvent::describe() const +{ + switch(type) { + case CONNEVENT_NONE: + return "CONNEVENT_NONE"; + case CONNEVENT_DATA_RECEIVED: + return "CONNEVENT_DATA_RECEIVED"; + case CONNEVENT_PEER_ADDED: + return "CONNEVENT_PEER_ADDED"; + case CONNEVENT_PEER_REMOVED: + return "CONNEVENT_PEER_REMOVED"; + case CONNEVENT_BIND_FAILED: + return "CONNEVENT_BIND_FAILED"; + } + return "Invalid ConnectionEvent"; +} + + +ConnectionEventPtr ConnectionEvent::create(ConnectionEventType type) +{ + return std::shared_ptr(new ConnectionEvent(type)); +} + +ConnectionEventPtr ConnectionEvent::dataReceived(session_t peer_id, const Buffer &data) +{ + auto e = create(CONNEVENT_DATA_RECEIVED); + e->peer_id = peer_id; + data.copyTo(e->data); + return e; +} + +ConnectionEventPtr ConnectionEvent::peerAdded(session_t peer_id, Address address) +{ + auto e = create(CONNEVENT_PEER_ADDED); + e->peer_id = peer_id; + e->address = address; + return e; +} + +ConnectionEventPtr ConnectionEvent::peerRemoved(session_t peer_id, bool is_timeout, Address address) +{ + auto e = create(CONNEVENT_PEER_REMOVED); + e->peer_id = peer_id; + e->timeout = is_timeout; + e->address = address; + return e; +} + +ConnectionEventPtr ConnectionEvent::bindFailed() +{ + return create(CONNEVENT_BIND_FAILED); +} + +/* + Connection +*/ + +Connection::Connection(u32 max_packet_size, float timeout, + bool ipv6, PeerHandler *peerhandler) : + m_udpSocket(ipv6), + m_protocol_id(PROTOCOL_ID), + m_sendThread(new ConnectionSendThread(max_packet_size, timeout)), + m_receiveThread(new ConnectionReceiveThread()), + m_bc_peerhandler(peerhandler) + +{ + /* Amount of time Receive() will wait for data, this is entirely different + * from the connection timeout */ + m_udpSocket.setTimeoutMs(500); + + m_sendThread->setParent(this); + m_receiveThread->setParent(this); + + m_sendThread->start(); + m_receiveThread->start(); +} + + +Connection::~Connection() +{ + m_shutting_down = true; + // request threads to stop + m_sendThread->stop(); + m_receiveThread->stop(); + + //TODO for some unkonwn reason send/receive threads do not exit as they're + // supposed to be but wait on peer timeout. To speed up shutdown we reduce + // timeout to half a second. + m_sendThread->setPeerTimeout(0.5); + + // wait for threads to finish + m_sendThread->wait(); + m_receiveThread->wait(); + + // Delete peers + for (auto &peer : m_peers) { + delete peer.second; + } +} + +/* Internal stuff */ + +void Connection::putEvent(ConnectionEventPtr e) +{ + assert(e->type != CONNEVENT_NONE); // Pre-condition + m_event_queue.push_back(e); +} + +void Connection::TriggerSend() +{ + m_sendThread->Trigger(); +} + +PeerHelper Connection::getPeerNoEx(session_t peer_id) +{ + MutexAutoLock peerlock(m_peers_mutex); + std::map::iterator node = m_peers.find(peer_id); + + if (node == m_peers.end()) { + return PeerHelper(NULL); + } + + // Error checking + FATAL_ERROR_IF(node->second->id != peer_id, "Invalid peer id"); + + return PeerHelper(node->second); +} + +/* find peer_id for address */ +session_t Connection::lookupPeer(const Address& sender) +{ + MutexAutoLock peerlock(m_peers_mutex); + for (auto &it: m_peers) { + Peer *peer = it.second; + if (peer->isPendingDeletion()) + continue; + + if (peer->getAddress() == sender) + return peer->id; + } + + return PEER_ID_INEXISTENT; +} + +u32 Connection::getActiveCount() +{ + MutexAutoLock peerlock(m_peers_mutex); + u32 count = 0; + for (auto &it : m_peers) { + Peer *peer = it.second; + if (peer->isPendingDeletion()) + continue; + if (peer->isHalfOpen()) + continue; + count++; + } + return count; +} + +bool Connection::deletePeer(session_t peer_id, bool timeout) +{ + Peer *peer = 0; + + /* lock list as short as possible */ + { + MutexAutoLock peerlock(m_peers_mutex); + if (m_peers.find(peer_id) == m_peers.end()) + return false; + peer = m_peers[peer_id]; + m_peers.erase(peer_id); + auto it = std::find(m_peer_ids.begin(), m_peer_ids.end(), peer_id); + m_peer_ids.erase(it); + } + + // Create event + putEvent(ConnectionEvent::peerRemoved(peer_id, timeout, peer->getAddress())); + + peer->Drop(); + return true; +} + +/* Interface */ + +ConnectionEventPtr Connection::waitEvent(u32 timeout_ms) +{ + try { + return m_event_queue.pop_front(timeout_ms); + } catch(ItemNotFoundException &ex) { + return ConnectionEvent::create(CONNEVENT_NONE); + } +} + +void Connection::putCommand(ConnectionCommandPtr c) +{ + if (!m_shutting_down) { + m_command_queue.push_back(c); + m_sendThread->Trigger(); + } +} + +void Connection::Serve(Address bind_addr) +{ + putCommand(ConnectionCommand::serve(bind_addr)); +} + +void Connection::Connect(Address address) +{ + putCommand(ConnectionCommand::connect(address)); +} + +bool Connection::Connected() +{ + MutexAutoLock peerlock(m_peers_mutex); + + if (m_peers.size() != 1) + return false; + + std::map::iterator node = m_peers.find(PEER_ID_SERVER); + if (node == m_peers.end()) + return false; + + if (m_peer_id == PEER_ID_INEXISTENT) + return false; + + return true; +} + +void Connection::Disconnect() +{ + putCommand(ConnectionCommand::disconnect()); +} + +bool Connection::ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms) +{ + /* + Note that this function can potentially wait infinitely if non-data + events keep happening before the timeout expires. + This is not considered to be a problem (is it?) + */ + for(;;) { + ConnectionEventPtr e_ptr = waitEvent(timeout_ms); + const ConnectionEvent &e = *e_ptr; + + if (e.type != CONNEVENT_NONE) { + LOG(dout_con << getDesc() << ": Receive: got event: " + << e.describe() << std::endl); + } + + switch (e.type) { + case CONNEVENT_NONE: + return false; + case CONNEVENT_DATA_RECEIVED: + // Data size is lesser than command size, ignoring packet + if (e.data.getSize() < 2) { + continue; + } + + pkt->putRawPacket(*e.data, e.data.getSize(), e.peer_id); + return true; + case CONNEVENT_PEER_ADDED: { + UDPPeer tmp(e.peer_id, e.address, this); + if (m_bc_peerhandler) + m_bc_peerhandler->peerAdded(&tmp); + continue; + } + case CONNEVENT_PEER_REMOVED: { + UDPPeer tmp(e.peer_id, e.address, this); + if (m_bc_peerhandler) + m_bc_peerhandler->deletingPeer(&tmp, e.timeout); + continue; + } + case CONNEVENT_BIND_FAILED: + throw ConnectionBindFailed("Failed to bind socket " + "(port already in use?)"); + } + } + return false; +} + +void Connection::Receive(NetworkPacket *pkt) +{ + bool any = ReceiveTimeoutMs(pkt, m_bc_receive_timeout); + if (!any) + throw NoIncomingDataException("No incoming data"); +} + +void Connection::Send(session_t peer_id, u8 channelnum, + NetworkPacket *pkt, bool reliable) +{ + assert(channelnum < CHANNEL_COUNT); // Pre-condition + + // approximate check similar to UDPPeer::processReliableSendCommand() + // to get nicer errors / backtraces if this happens. + if (reliable && pkt->getSize() > MAX_RELIABLE_WINDOW_SIZE*512) { + std::ostringstream oss; + oss << "Packet too big for window, peer_id=" << peer_id + << " command=" << pkt->getCommand() << " size=" << pkt->getSize(); + FATAL_ERROR(oss.str().c_str()); + } + + putCommand(ConnectionCommand::send(peer_id, channelnum, pkt, reliable)); +} + +Address Connection::GetPeerAddress(session_t peer_id) +{ + PeerHelper peer = getPeerNoEx(peer_id); + + if (!peer) + throw PeerNotFoundException("No address for peer found!"); + return peer->getAddress(); +} + +float Connection::getPeerStat(session_t peer_id, rtt_stat_type type) +{ + PeerHelper peer = getPeerNoEx(peer_id); + if (!peer) + return -1; + return peer->getStat(type); +} + +float Connection::getLocalStat(rate_stat_type type) +{ + PeerHelper peer = getPeerNoEx(PEER_ID_SERVER); + + FATAL_ERROR_IF(!peer, "Connection::getLocalStat we couldn't get our own peer? are you serious???"); + + float retval = 0; + + for (Channel &channel : dynamic_cast(&peer)->channels) { + switch(type) { + case CUR_DL_RATE: + retval += channel.getCurrentDownloadRateKB(); + break; + case AVG_DL_RATE: + retval += channel.getAvgDownloadRateKB(); + break; + case CUR_INC_RATE: + retval += channel.getCurrentIncomingRateKB(); + break; + case AVG_INC_RATE: + retval += channel.getAvgIncomingRateKB(); + break; + case AVG_LOSS_RATE: + retval += channel.getAvgLossRateKB(); + break; + case CUR_LOSS_RATE: + retval += channel.getCurrentLossRateKB(); + break; + default: + FATAL_ERROR("Connection::getLocalStat Invalid stat type"); + } + } + return retval; +} + +session_t Connection::createPeer(const Address &sender, int fd) +{ + // Somebody wants to make a new connection + + // Get a unique peer id + const session_t minimum = 2; + const session_t overflow = MAX_UDP_PEERS; + + /* + Find an unused peer id + */ + + MutexAutoLock lock(m_peers_mutex); + session_t peer_id_new; + for (int tries = 0; tries < 100; tries++) { + peer_id_new = myrand_range(minimum, overflow - 1); + if (m_peers.find(peer_id_new) == m_peers.end()) + break; + } + if (m_peers.find(peer_id_new) != m_peers.end()) { + errorstream << getDesc() << " ran out of peer ids" << std::endl; + return PEER_ID_INEXISTENT; + } + + // Create a peer + Peer *peer = 0; + peer = new UDPPeer(peer_id_new, sender, this); + + m_peers[peer->id] = peer; + m_peer_ids.push_back(peer->id); + + LOG(dout_con << getDesc() + << "createPeer(): giving peer_id=" << peer_id_new << std::endl); + + { + Buffer reply(4); + writeU8(&reply[0], PACKET_TYPE_CONTROL); + writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID); + writeU16(&reply[2], peer_id_new); + putCommand(ConnectionCommand::createPeer(peer_id_new, reply)); + } + + // Create peer addition event + putEvent(ConnectionEvent::peerAdded(peer_id_new, sender)); + + // We're now talking to a valid peer_id + return peer_id_new; +} + +const std::string Connection::getDesc() +{ + MutexAutoLock _(m_info_mutex); + return std::string("con(")+ + itos(m_udpSocket.GetHandle())+"/"+itos(m_peer_id)+")"; +} + +void Connection::DisconnectPeer(session_t peer_id) +{ + putCommand(ConnectionCommand::disconnect_peer(peer_id)); +} + +void Connection::doResendOne(session_t peer_id) +{ + assert(peer_id != PEER_ID_INEXISTENT); + putCommand(ConnectionCommand::resend_one(peer_id)); +} + +void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum) +{ + assert(channelnum < CHANNEL_COUNT); // Pre-condition + + LOG(dout_con< ack(4); + writeU8(&ack[0], PACKET_TYPE_CONTROL); + writeU8(&ack[1], CONTROLTYPE_ACK); + writeU16(&ack[2], seqnum); + + putCommand(ConnectionCommand::ack(peer_id, channelnum, ack)); + m_sendThread->Trigger(); +} + +UDPPeer* Connection::createServerPeer(const Address &address) +{ + if (ConnectedToServer()) + throw ConnectionException("Already connected to a server"); + + UDPPeer *peer = new UDPPeer(PEER_ID_SERVER, address, this); + peer->SetFullyOpen(); + + { + MutexAutoLock lock(m_peers_mutex); + m_peers[peer->id] = peer; + m_peer_ids.push_back(peer->id); + } + + return peer; +} + +} // namespace diff --git a/src/network/mtp/impl.h b/src/network/mtp/impl.h new file mode 100644 index 000000000..0232ebc0d --- /dev/null +++ b/src/network/mtp/impl.h @@ -0,0 +1,325 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "network/connection.h" +#include "network/socket.h" +#include "constants.h" +#include "util/pointer.h" +#include "util/container.h" +#include "util/numeric.h" +#include "porting.h" +#include "network/networkprotocol.h" +#include +#include +#include + +namespace con +{ + +class ConnectionReceiveThread; +class ConnectionSendThread; + +class Peer; + +// FIXME: Peer refcounting should generally be replaced by std::shared_ptr +class PeerHelper +{ +public: + PeerHelper() = default; + inline PeerHelper(Peer *peer) { *this = peer; } + ~PeerHelper(); + + PeerHelper& operator=(Peer *peer); + inline Peer* operator->() const { return m_peer; } + inline Peer* operator&() const { return m_peer; } + + inline bool operator!() { return !m_peer; } + inline bool operator!=(std::nullptr_t) { return !!m_peer; } + +private: + Peer *m_peer = nullptr; +}; + +/* + Connection +*/ + +enum ConnectionEventType { + CONNEVENT_NONE, + CONNEVENT_DATA_RECEIVED, + CONNEVENT_PEER_ADDED, + CONNEVENT_PEER_REMOVED, + CONNEVENT_BIND_FAILED, +}; + +struct ConnectionEvent; +typedef std::shared_ptr ConnectionEventPtr; + +// This is very similar to ConnectionCommand +struct ConnectionEvent +{ + const ConnectionEventType type; + session_t peer_id = 0; + Buffer data; + bool timeout = false; + Address address; + + // We don't want to copy "data" + DISABLE_CLASS_COPY(ConnectionEvent); + + static ConnectionEventPtr create(ConnectionEventType type); + static ConnectionEventPtr dataReceived(session_t peer_id, const Buffer &data); + static ConnectionEventPtr peerAdded(session_t peer_id, Address address); + static ConnectionEventPtr peerRemoved(session_t peer_id, bool is_timeout, Address address); + static ConnectionEventPtr bindFailed(); + + const char *describe() const; + +private: + ConnectionEvent(ConnectionEventType type_) : + type(type_) {} +}; + +struct ConnectionCommand; +typedef std::shared_ptr ConnectionCommandPtr; + +struct BufferedPacket; +typedef std::shared_ptr BufferedPacketPtr; + +class Connection; +class PeerHandler; + +class Peer : public IPeer { + public: + friend class PeerHelper; + + virtual ~Peer() { + MutexAutoLock usage_lock(m_exclusive_access_mutex); + FATAL_ERROR_IF(m_usage != 0, "Reference counting failure"); + } + + void Drop(); + + virtual void PutReliableSendCommand(ConnectionCommandPtr &c, + unsigned int max_packet_size) {}; + + bool isPendingDeletion() const { + MutexAutoLock lock(m_exclusive_access_mutex); + return m_pending_deletion; + } + void ResetTimeout() { + MutexAutoLock lock(m_exclusive_access_mutex); + m_timeout_counter = 0; + } + + bool isHalfOpen() const { + MutexAutoLock lock(m_exclusive_access_mutex); + return m_half_open; + } + void SetFullyOpen() { + MutexAutoLock lock(m_exclusive_access_mutex); + m_half_open = false; + } + + virtual bool isTimedOut(float timeout, std::string &reason); + + unsigned int m_increment_packets_remaining = 0; + + virtual u16 getNextSplitSequenceNumber(u8 channel) { return 0; }; + virtual void setNextSplitSequenceNumber(u8 channel, u16 seqnum) {}; + virtual SharedBuffer addSplitPacket(u8 channel, BufferedPacketPtr &toadd, + bool reliable) + { + FATAL_ERROR("unimplemented in abstract class"); + } + + virtual bool Ping(float dtime, SharedBuffer& data) { return false; }; + + virtual float getStat(rtt_stat_type type) const { + switch (type) { + case MIN_RTT: + return m_rtt.min_rtt; + case MAX_RTT: + return m_rtt.max_rtt; + case AVG_RTT: + return m_rtt.avg_rtt; + case MIN_JITTER: + return m_rtt.jitter_min; + case MAX_JITTER: + return m_rtt.jitter_max; + case AVG_JITTER: + return m_rtt.jitter_avg; + } + return -1; + } + + protected: + Peer(session_t id, const Address &address, Connection *connection) : + IPeer(id), + m_connection(connection), + address(address), + m_last_timeout_check(porting::getTimeMs()) + { + } + + virtual void reportRTT(float rtt) {}; + + void RTTStatistics(float rtt, + const std::string &profiler_id = "", + unsigned int num_samples = 1000); + + bool IncUseCount(); + void DecUseCount(); + + mutable std::mutex m_exclusive_access_mutex; + + bool m_pending_deletion = false; + + Connection *m_connection; + + // Address of the peer + Address address; + + // Ping timer + float m_ping_timer = 0.0f; + + private: + struct rttstats { + float jitter_min = FLT_MAX; + float jitter_max = 0.0f; + float jitter_avg = -1.0f; + float min_rtt = FLT_MAX; + float max_rtt = 0.0f; + float avg_rtt = -1.0f; + }; + + rttstats m_rtt; + float m_last_rtt = -1.0f; + + /* + Until the peer has communicated with us using their assigned peer id + the connection is considered half-open. + During this time we inhibit re-sending any reliables or pings. This + is to avoid spending too many resources on a potential DoS attack + and to make sure Minetest servers are not useful for UDP amplificiation. + */ + bool m_half_open = true; + + // current usage count + unsigned int m_usage = 0; + + // Seconds from last receive + float m_timeout_counter = 0.0f; + + u64 m_last_timeout_check; +}; + +class UDPPeer; + +class Connection : public IConnection +{ +public: + friend class ConnectionSendThread; + friend class ConnectionReceiveThread; + + Connection(u32 max_packet_size, float timeout, bool ipv6, + PeerHandler *peerhandler); + ~Connection(); + + /* Interface */ + ConnectionEventPtr waitEvent(u32 timeout_ms); + + void putCommand(ConnectionCommandPtr c); + + void SetTimeoutMs(u32 timeout) { m_bc_receive_timeout = timeout; } + void Serve(Address bind_addr); + void Connect(Address address); + bool Connected(); + void Disconnect(); + bool ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms); + void Receive(NetworkPacket *pkt); + void Send(session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable); + session_t GetPeerID() const { return m_peer_id; } + Address GetPeerAddress(session_t peer_id); + float getPeerStat(session_t peer_id, rtt_stat_type type); + float getLocalStat(rate_stat_type type); + u32 GetProtocolID() const { return m_protocol_id; }; + const std::string getDesc(); + void DisconnectPeer(session_t peer_id); + +protected: + PeerHelper getPeerNoEx(session_t peer_id); + session_t lookupPeer(const Address& sender); + + session_t createPeer(const Address& sender, int fd); + UDPPeer* createServerPeer(const Address& sender); + bool deletePeer(session_t peer_id, bool timeout); + + void SetPeerID(session_t id) { m_peer_id = id; } + + void doResendOne(session_t peer_id); + + void sendAck(session_t peer_id, u8 channelnum, u16 seqnum); + + std::vector getPeerIDs() + { + MutexAutoLock peerlock(m_peers_mutex); + return m_peer_ids; + } + + u32 getActiveCount(); + + UDPSocket m_udpSocket; + // Command queue: user -> SendThread + MutexedQueue m_command_queue; + + void putEvent(ConnectionEventPtr e); + + void TriggerSend(); + + bool ConnectedToServer() + { + return getPeerNoEx(PEER_ID_SERVER) != nullptr; + } +private: + // Event queue: ReceiveThread -> user + MutexedQueue m_event_queue; + + session_t m_peer_id = 0; + u32 m_protocol_id; + + std::map m_peers; + std::vector m_peer_ids; + std::mutex m_peers_mutex; + + std::unique_ptr m_sendThread; + std::unique_ptr m_receiveThread; + + mutable std::mutex m_info_mutex; + + // Backwards compatibility + PeerHandler *m_bc_peerhandler; + u32 m_bc_receive_timeout = 0; + + bool m_shutting_down = false; +}; + +} // namespace diff --git a/src/network/connection_internal.h b/src/network/mtp/internal.h similarity index 95% rename from src/network/connection_internal.h rename to src/network/mtp/internal.h index a528f3fef..f6ab1f159 100644 --- a/src/network/connection_internal.h +++ b/src/network/mtp/internal.h @@ -19,11 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -/********************************************/ -/* may only be included from in src/network */ -/********************************************/ +#include "network/mtp/impl.h" -#include "connection.h" +// Constant that differentiates the protocol from random data and other protocols +#define PROTOCOL_ID 0x4f457403 #define MAX_UDP_PEERS 65535 @@ -161,6 +160,32 @@ inline float CALC_DTIME(u64 lasttime, u64 curtime) return MYMAX(MYMIN(value, 0.1f), 0.0f); } +/* Exceptions */ + +class NotFoundException : public BaseException +{ +public: + NotFoundException(const char *s) : BaseException(s) {} +}; + +class ProcessedSilentlyException : public BaseException +{ +public: + ProcessedSilentlyException(const char *s) : BaseException(s) {} +}; + +class ProcessedQueued : public BaseException +{ +public: + ProcessedQueued(const char *s) : BaseException(s) {} +}; + +class IncomingDataCorruption : public BaseException +{ +public: + IncomingDataCorruption(const char *s) : BaseException(s) {} +}; + /* Struct for all kinds of packets. Includes following data: diff --git a/src/network/connectionthreads.cpp b/src/network/mtp/threads.cpp similarity index 99% rename from src/network/connectionthreads.cpp rename to src/network/mtp/threads.cpp index d5c9a39ed..c7c728c9d 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/mtp/threads.cpp @@ -18,7 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "connectionthreads.h" +#include "network/mtp/threads.h" #include "log.h" #include "profiler.h" #include "settings.h" diff --git a/src/network/connectionthreads.h b/src/network/mtp/threads.h similarity index 99% rename from src/network/connectionthreads.h rename to src/network/mtp/threads.h index fff71f657..b41307fa6 100644 --- a/src/network/connectionthreads.h +++ b/src/network/mtp/threads.h @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "threading/thread.h" -#include "connection_internal.h" +#include "network/mtp/internal.h" namespace con { diff --git a/src/network/networkexceptions.h b/src/network/networkexceptions.h index 58a3bb490..2e9c2a6e8 100644 --- a/src/network/networkexceptions.h +++ b/src/network/networkexceptions.h @@ -26,11 +26,6 @@ namespace con /* Exceptions */ -class NotFoundException : public BaseException -{ -public: - NotFoundException(const char *s) : BaseException(s) {} -}; class PeerNotFoundException : public BaseException { @@ -62,23 +57,6 @@ public: NoIncomingDataException(const char *s) : BaseException(s) {} }; -class ProcessedSilentlyException : public BaseException -{ -public: - ProcessedSilentlyException(const char *s) : BaseException(s) {} -}; - -class ProcessedQueued : public BaseException -{ -public: - ProcessedQueued(const char *s) : BaseException(s) {} -}; - -class IncomingDataCorruption : public BaseException -{ -public: - IncomingDataCorruption(const char *s) : BaseException(s) {} -}; } class SocketException : public BaseException diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index d84c735b8..743aac831 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -237,9 +237,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CLIENT_PROTOCOL_VERSION_MIN 37 #define CLIENT_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION -// Constant that differentiates the protocol from random data and other protocols -#define PROTOCOL_ID 0x4f457403 - #define PASSWORD_SIZE 28 // Maximum password length. Allows for // base64-encoded SHA-1 (27+\0). diff --git a/src/network/peerhandler.h b/src/network/peerhandler.h index da65483ef..1e00249ca 100644 --- a/src/network/peerhandler.h +++ b/src/network/peerhandler.h @@ -24,17 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., namespace con { -typedef enum -{ - MIN_RTT, - MAX_RTT, - AVG_RTT, - MIN_JITTER, - MAX_JITTER, - AVG_JITTER -} rtt_stat_type; - -class Peer; +class IPeer; class PeerHandler { @@ -47,13 +37,13 @@ public: This is called after the Peer has been inserted into the Connection's peer container. */ - virtual void peerAdded(Peer *peer) = 0; + virtual void peerAdded(IPeer *peer) = 0; /* This is called before the Peer has been removed from the Connection's peer container. */ - virtual void deletingPeer(Peer *peer, bool timeout) = 0; + virtual void deletingPeer(IPeer *peer, bool timeout) = 0; }; enum PeerChangeType : u8 diff --git a/src/server.cpp b/src/server.cpp index 1d98e4634..a78244e99 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -258,11 +258,7 @@ Server::Server( m_simple_singleplayer_mode(simple_singleplayer_mode), m_dedicated(dedicated), m_async_fatal_error(""), - m_con(std::make_shared(PROTOCOL_ID, - 512, - CONNECTION_TIMEOUT, - m_bind_addr.isIPv6(), - this)), + m_con(con::createMTP(CONNECTION_TIMEOUT, m_bind_addr.isIPv6(), this)), m_itemdef(createItemDefManager()), m_nodedef(createNodeDefManager()), m_craftdef(createCraftDefManager()), @@ -1258,7 +1254,7 @@ void Server::onMapEditEvent(const MapEditEvent &event) m_unsent_map_edit_queue.push(new MapEditEvent(event)); } -void Server::peerAdded(con::Peer *peer) +void Server::peerAdded(con::IPeer *peer) { verbosestream<<"Server::peerAdded(): peer->id=" <id<id, false)); } -void Server::deletingPeer(con::Peer *peer, bool timeout) +void Server::deletingPeer(con::IPeer *peer, bool timeout) { verbosestream<<"Server::deletingPeer(): peer->id=" <id<<", timeout="< m_con; + std::shared_ptr m_con; // Ban checking BanManager *m_banmanager = nullptr; diff --git a/src/server/clientiface.cpp b/src/server/clientiface.cpp index e5c07b3d8..0e5140f00 100644 --- a/src/server/clientiface.cpp +++ b/src/server/clientiface.cpp @@ -648,13 +648,14 @@ void RemoteClient::setLangCode(const std::string &code) m_lang_code = string_sanitize_ascii(code, 12); } -ClientInterface::ClientInterface(const std::shared_ptr & con) +ClientInterface::ClientInterface(const std::shared_ptr &con) : m_con(con), m_env(nullptr) { } + ClientInterface::~ClientInterface() { /* diff --git a/src/server/clientiface.h b/src/server/clientiface.h index ac41b00ca..d4d21ceb2 100644 --- a/src/server/clientiface.h +++ b/src/server/clientiface.h @@ -168,7 +168,7 @@ class EmergeManager; */ namespace con { - class Connection; + class IConnection; } @@ -464,7 +464,7 @@ public: friend class Server; - ClientInterface(const std::shared_ptr &con); + ClientInterface(const std::shared_ptr &con); ~ClientInterface(); /* run sync step */ @@ -543,7 +543,7 @@ private: void UpdatePlayerList(); // Connection - std::shared_ptr m_con; + std::shared_ptr m_con; std::recursive_mutex m_clients_mutex; // Connected clients (behind the con mutex) RemoteClientMap m_clients; diff --git a/src/unittest/test_connection.cpp b/src/unittest/test_connection.cpp index 4adbc9039..3c0043389 100644 --- a/src/unittest/test_connection.cpp +++ b/src/unittest/test_connection.cpp @@ -23,7 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "settings.h" #include "util/serialize.h" -#include "network/connection_internal.h" +#include "network/peerhandler.h" +#include "network/mtp/internal.h" #include "network/networkpacket.h" #include "network/socket.h" @@ -59,7 +60,7 @@ struct Handler : public con::PeerHandler { Handler(const char *a_name) : name(a_name) {} - void peerAdded(con::Peer *peer) + void peerAdded(con::IPeer *peer) { infostream << "Handler(" << name << ")::peerAdded(): " "id=" << peer->id << std::endl; @@ -67,7 +68,7 @@ struct Handler : public con::PeerHandler count++; } - void deletingPeer(con::Peer *peer, bool timeout) + void deletingPeer(con::IPeer *peer, bool timeout) { infostream << "Handler(" << name << ")::deletingPeer(): " "id=" << peer->id << ", timeout=" << timeout << std::endl; @@ -165,8 +166,6 @@ void TestConnection::testConnectSendReceive() NOTE: This mostly tests the legacy interface. */ - u32 proto_id = 0xad26846a; - Handler hand_server("server"); Handler hand_client("client"); @@ -187,11 +186,11 @@ void TestConnection::testConnectSendReceive() } infostream << "** Creating server Connection" << std::endl; - con::Connection server(proto_id, 512, 5.0, false, &hand_server); + con::Connection server(512, 5.0f, false, &hand_server); server.Serve(address); infostream << "** Creating client Connection" << std::endl; - con::Connection client(proto_id, 512, 5.0, false, &hand_client); + con::Connection client(512, 5.0f, false, &hand_client); UASSERT(hand_server.count == 0); UASSERT(hand_client.count == 0); From 274c223d00519c731fea7de960aac9879273ad8a Mon Sep 17 00:00:00 2001 From: Zemtzov7 <72821250+zmv7@users.noreply.github.com> Date: Fri, 23 Aug 2024 02:15:55 +0500 Subject: [PATCH 023/200] Fix CSM help form using "/" instead of "." (#15034) when copying commands to chat --- builtin/common/information_formspecs.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/common/information_formspecs.lua b/builtin/common/information_formspecs.lua index 3fa397d25..270631fc9 100644 --- a/builtin/common/information_formspecs.lua +++ b/builtin/common/information_formspecs.lua @@ -69,7 +69,7 @@ local function build_chatcommands_formspec(name, sel, copy) description = cmds[2].description if copy then local msg = S("Command: @1 @2", - core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params) + core.colorize("#0FF", (INIT == "client" and "." or "/") .. cmds[1]), cmds[2].params) if INIT == "client" then core.display_chat_message(msg) else From 56123b2fbe1ad4e7878556f8d51c551d84fb92e7 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 23 Aug 2024 18:46:12 +0200 Subject: [PATCH 024/200] Fix bounding box of clouds fixes #15031 --- src/client/clouds.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/clouds.h b/src/client/clouds.h index d93fa9b43..e288c5e8a 100644 --- a/src/client/clouds.h +++ b/src/client/clouds.h @@ -133,8 +133,8 @@ private: { float height_bs = m_params.height * BS; float thickness_bs = m_params.thickness * BS; - m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, -BS * 1000000.0f, - BS * 1000000.0f, height_bs + thickness_bs - BS * m_camera_offset.Y, BS * 1000000.0f); + m_box = aabb3f(-BS * 1000000.0f, height_bs, -BS * 1000000.0f, + BS * 1000000.0f, height_bs + thickness_bs, BS * 1000000.0f); } void updateMesh(); From df8afe3dc418534111c0f2d7f0c67bf1caf6cda9 Mon Sep 17 00:00:00 2001 From: wsor4035 <24964441+wsor4035@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:32:42 -0400 Subject: [PATCH 025/200] Reword CMake message for LuaJIT detection --- cmake/Modules/FindLua.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/FindLua.cmake b/cmake/Modules/FindLua.cmake index be5d92d8c..a239046ac 100644 --- a/cmake/Modules/FindLua.cmake +++ b/cmake/Modules/FindLua.cmake @@ -11,7 +11,7 @@ if(ENABLE_LUAJIT) find_package(LuaJIT) if(LUAJIT_FOUND) set(USE_LUAJIT TRUE) - message (STATUS "Using LuaJIT provided by system.") + message (STATUS "Using LuaJIT") elseif(REQUIRE_LUAJIT) message(FATAL_ERROR "LuaJIT not found whereas REQUIRE_LUAJIT=\"TRUE\" is used.\n" "To continue, either install LuaJIT or do not use REQUIRE_LUAJIT=\"TRUE\".") From da1fc9a5366b9ceace6d829783a9a56545616158 Mon Sep 17 00:00:00 2001 From: Desour Date: Sun, 25 Aug 2024 10:21:50 +0200 Subject: [PATCH 026/200] Meshgen: Don't get lights for not drawn solid faces `drawCuboid()` doesn't call the face lighter function for masked faces, so we don't need these values. This is for performance. --- src/client/content_mapblock.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index c351c4b80..1de36c9c5 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -463,6 +463,8 @@ void MapblockMeshGenerator::drawSolidNode() if (data->m_smooth_lighting) { LightPair lights[6][4]; for (int face = 0; face < 6; ++face) { + if (mask & (1 << face)) + continue; for (int k = 0; k < 4; k++) { v3s16 corner = light_dirs[light_indices[face][k]]; lights[face][k] = LightPair(getSmoothLightSolid( From 5583831c40d51e5f63540ccfcda71b678564c21b Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 24 Aug 2024 10:50:44 +0200 Subject: [PATCH 027/200] zstd: Fix minetest.decompress lockup when data ends too early --- src/serialization.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/serialization.cpp b/src/serialization.cpp index 4134126ca..0319b0159 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -262,6 +262,8 @@ void decompressZstd(std::istream &is, std::ostream &os) is.read(input_buffer, bufsize); input.size = is.gcount(); input.pos = 0; + if (input.size == 0) + throw SerializationError("decompressZstd: data ended too early"); } ret = ZSTD_decompressStream(stream.get(), &output, &input); From 21ed680b10aef84f58222a427aebffab48efa171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Mon, 26 Aug 2024 21:22:38 +0200 Subject: [PATCH 028/200] Make getting bone overrides return the "same" euler angles (#15007) --- games/devtest/mods/unittests/entity.lua | 20 ++++++++++++++++++++ src/activeobject.h | 3 +++ src/script/lua_api/l_object.cpp | 24 ++++++++++++++---------- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/games/devtest/mods/unittests/entity.lua b/games/devtest/mods/unittests/entity.lua index 8e8bd1c48..af91a2a94 100644 --- a/games/devtest/mods/unittests/entity.lua +++ b/games/devtest/mods/unittests/entity.lua @@ -214,3 +214,23 @@ unittests.register("test_objects_in_area", function(_, pos) return core.objects_in_area(pos:offset(-1, -1, -1), pos:offset(1, 1, 1)) end) end, {map=true}) + +-- Tests that bone rotation euler angles are preserved (see #14992) +local function test_get_bone_rot(_, pos) + local obj = core.add_entity(pos, "unittests:dummy") + for _ = 1, 100 do + local function assert_similar(euler_angles) + local _, rot = obj:get_bone_position("bonename") + assert(euler_angles:distance(rot) < 1e-3) + local override = obj:get_bone_override("bonename") + assert(euler_angles:distance(override.rotation.vec:apply(math.deg)) < 1e-3) + end + local deg = 1e3 * vector.new(math.random(), math.random(), math.random()) + obj:set_bone_position("bonename", vector.zero(), deg) + assert_similar(deg) + local rad = 3 * math.pi * vector.new(math.random(), math.random(), math.random()) + obj:set_bone_override("bonename", {rotation = {vec = rad}}) + assert_similar(rad:apply(math.deg)) + end +end +unittests.register("test_get_bone_rot", test_get_bone_rot, {map=true}) diff --git a/src/activeobject.h b/src/activeobject.h index 989b48e91..cb868346a 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -96,6 +96,9 @@ struct BoneOverride { core::quaternion previous; core::quaternion next; + // Redundantly store the euler angles serverside + // so that we can return them in the appropriate getters + v3f next_radians; bool absolute = false; f32 interp_timer = 0; } rotation; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index eb0a375d4..db7212897 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -562,8 +562,10 @@ int ObjectRef::l_set_bone_position(lua_State *L) BoneOverride props; if (!lua_isnoneornil(L, 3)) props.position.vector = check_v3f(L, 3); - if (!lua_isnoneornil(L, 4)) - props.rotation.next = core::quaternion(check_v3f(L, 4) * core::DEGTORAD); + if (!lua_isnoneornil(L, 4)) { + props.rotation.next_radians = check_v3f(L, 4) * core::DEGTORAD; + props.rotation.next = core::quaternion(props.rotation.next_radians); + } props.position.absolute = true; props.rotation.absolute = true; sao->setBoneOverride(bone, props); @@ -585,9 +587,9 @@ int ObjectRef::l_get_bone_position(lua_State *L) std::string bone = readParam(L, 2, ""); BoneOverride props = sao->getBoneOverride(bone); push_v3f(L, props.position.vector); - v3f euler_rot; - props.rotation.next.toEuler(euler_rot); - push_v3f(L, euler_rot * core::RADTODEG); + // In order to give modders back the euler angles they passed in, + // this **must not** compute equivalent euler angles from the quaternion + push_v3f(L, props.rotation.next_radians * core::RADTODEG); return 2; } @@ -633,8 +635,10 @@ int ObjectRef::l_set_bone_override(lua_State *L) lua_getfield(L, 3, "rotation"); if (!lua_isnil(L, -1)) { lua_getfield(L, -1, "vec"); - if (!lua_isnil(L, -1)) - props.rotation.next = core::quaternion(check_v3f(L, -1)); + if (!lua_isnil(L, -1)) { + props.rotation.next_radians = check_v3f(L, -1); + props.rotation.next = core::quaternion(props.rotation.next_radians); + } lua_pop(L, 1); read_prop_attrs(props.rotation); @@ -672,9 +676,9 @@ static void push_bone_override(lua_State *L, const BoneOverride &props) push_prop("position", props.position, props.position.vector); - v3f euler_rot; - props.rotation.next.toEuler(euler_rot); - push_prop("rotation", props.rotation, euler_rot); + // In order to give modders back the euler angles they passed in, + // this **must not** compute equivalent euler angles from the quaternion + push_prop("rotation", props.rotation, props.rotation.next_radians); push_prop("scale", props.scale, props.scale.vector); From 8109563a02cbe4c6c3bff224d808e1061f3e4386 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 26 Aug 2024 21:23:12 +0200 Subject: [PATCH 029/200] LocalPlayer: Restore 2u height sneak jump (#15015) Fix 1: Do not consider LocalPlayer's CAO in the collision data. Fix 2: work around the "aabbox3d::intersectsWithBox" edge-case. --- src/client/localplayer.cpp | 8 ++++---- src/collision.cpp | 16 +++++++++------- src/collision.h | 9 +++++++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index aa335e90e..75f755578 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -360,7 +360,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, collisionMoveResult result = collisionMoveSimple(env, m_client, pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f); + &position, &m_speed, accel_f, m_cao); bool could_sneak = control.sneak && !free_move && !in_liquid && !is_climbing && physics_override.sneak; @@ -444,7 +444,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, v3f check_pos = position; check_pos.Y += y_diff * dtime * 22.0f + BS * 0.01f; if (y_diff < BS * 0.6f || (physics_override.sneak_glitch - && !collision_check_intersection(env, m_client, m_collisionbox, check_pos))) { + && !collision_check_intersection(env, m_client, m_collisionbox, check_pos, m_cao))) { // Smoothen the movement (based on 'position.Y = bmax.Y') position.Y = std::min(check_pos.Y, bmax.Y); m_speed.Y = 0.0f; @@ -990,7 +990,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, collisionMoveResult result = collisionMoveSimple(env, m_client, pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f); + &position, &m_speed, accel_f, m_cao); // Position was slightly changed; update standing node pos if (touching_ground) @@ -1254,7 +1254,7 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, // try at peak of jump, zero step height collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d, - m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, v3f(0.0f)); + m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, v3f(0.0f), m_cao); // see if we can get a little bit farther horizontally if we had // jumped diff --git a/src/collision.cpp b/src/collision.cpp index d804f7f0d..f554dac80 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -273,7 +273,7 @@ static void add_object_boxes(Environment *env, const v3f pos_f, const v3f speed_f, ActiveObject *self, std::vector &cinfo) { - auto process_object = [&] (ActiveObject *object) { + auto process_object = [&cinfo] (ActiveObject *object) { if (object && object->collideWithObjects()) { aabb3f box; if (object->getCollisionBox(&box)) @@ -292,7 +292,7 @@ static void add_object_boxes(Environment *env, c_env->getActiveObjects(pos_f, distance, clientobjects); for (auto &clientobject : clientobjects) { - // Do collide with everything but itself and the parent CAO + // Do collide with everything but itself and children if (!self || (self != clientobject.obj && self != clientobject.obj->getParent())) { process_object(clientobject.obj); @@ -301,12 +301,12 @@ static void add_object_boxes(Environment *env, // add collision with local player LocalPlayer *lplayer = c_env->getLocalPlayer(); - if (lplayer->getParent() == nullptr) { + auto *obj = (ClientActiveObject*) lplayer->getCAO(); + if (!self || (self != obj && self != obj->getParent())) { aabb3f lplayer_collisionbox = lplayer->getCollisionbox(); v3f lplayer_pos = lplayer->getPosition(); lplayer_collisionbox.MinEdge += lplayer_pos; lplayer_collisionbox.MaxEdge += lplayer_pos; - auto *obj = (ActiveObject*) lplayer->getCAO(); cinfo.emplace_back(obj, 0, lplayer_collisionbox); } } @@ -315,7 +315,7 @@ static void add_object_boxes(Environment *env, { ServerEnvironment *s_env = dynamic_cast(env); if (s_env) { - // search for objects which are not us, or we are not its parent. + // search for objects which are not us and not our children. // we directly process the object in this callback to avoid useless // looping afterwards. auto include_obj_cb = [self, &process_object] (ServerActiveObject *obj) { @@ -623,8 +623,10 @@ bool collision_check_intersection(Environment *env, IGameDef *gamedef, Collision detection */ aabb3f checkbox = box_0; - checkbox.MinEdge += pos_f; - checkbox.MaxEdge += pos_f; + // aabbox3d::intersectsWithBox(box) returns true when the faces are touching perfectly. + // However, we do not want want a true-ish return value in that case. Add some tolerance. + checkbox.MinEdge += pos_f + (0.1f * BS); + checkbox.MaxEdge += pos_f - (0.1f * BS); /* Go through every node and object box diff --git a/src/collision.h b/src/collision.h index b29a222c3..a698e6328 100644 --- a/src/collision.h +++ b/src/collision.h @@ -65,7 +65,8 @@ struct collisionMoveResult std::vector collisions; }; -// Moves using a single iteration; speed should not exceed pos_max_d/dtime +/// @brief Moves using a single iteration; speed should not exceed pos_max_d/dtime +/// @param self (optional) ActiveObject to ignore in the collision detection. collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef, f32 pos_max_d, const aabb3f &box_0, f32 stepheight, f32 dtime, @@ -73,7 +74,11 @@ collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef, v3f accel_f, ActiveObject *self=NULL, bool collide_with_objects=true); -// check if box is in collision on actual position +/// @brief A simpler version of "collisionMoveSimple" that only checks whether +/// a collision occurs at the given position. +/// @param self (optional) ActiveObject to ignore in the collision detection. +/// @returns `true` when `box_0` truly intersects with a node or object. +/// Touching faces are not counted as intersection. bool collision_check_intersection(Environment *env, IGameDef *gamedef, const aabb3f &box_0, const v3f &pos_f, ActiveObject *self = nullptr, bool collide_with_objects = true); From 5d18b6fcd0960fa495164bd8ac91b39e2d3b4b2e Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Tue, 27 Aug 2024 16:33:50 +0200 Subject: [PATCH 030/200] Fix incorrect documentation of new-style particlespawner size property --- doc/lua_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index a43f517a0..ae046dd3a 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -10999,7 +10999,7 @@ Types used are defined in the previous section. * vec3 range `acc`: the direction and speed with which the particle accelerates -* vec3 range `size`: scales the visual size of the particle texture. +* float range `size`: scales the visual size of the particle texture. if `node` is set, this can be set to 0 to spawn randomly-sized particles (just like actual node dig particles). From c893e0b72b718a851dde39ef1e5c5a1cb0b1c780 Mon Sep 17 00:00:00 2001 From: JosiahWI <41302989+JosiahWI@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:36:02 -0500 Subject: [PATCH 031/200] Convert nodedef tests to Catch2 (#15045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> --- src/unittest/test_nodedef.cpp | 36 +++++++++-------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/src/unittest/test_nodedef.cpp b/src/unittest/test_nodedef.cpp index acf669783..85dadd17d 100644 --- a/src/unittest/test_nodedef.cpp +++ b/src/unittest/test_nodedef.cpp @@ -17,38 +17,22 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "test.h" - -#include - #include "gamedef.h" #include "nodedef.h" #include "network/networkprotocol.h" -class TestNodeDef : public TestBase -{ -public: - TestNodeDef() { TestManager::registerTestModule(this); } - const char *getName() { return "TestNodeDef"; } +#include - void runTests(IGameDef *gamedef); +#include +#include - void testContentFeaturesSerialization(); -}; -static TestNodeDef g_test_instance; - -void TestNodeDef::runTests(IGameDef *gamedef) -{ - TEST(testContentFeaturesSerialization); -} - -//////////////////////////////////////////////////////////////////////////////// - -void TestNodeDef::testContentFeaturesSerialization() +TEST_CASE("Given a node definition, " + "when we serialize and then deserialize it, " + "then the deserialized one should be equal to the original.", + "[nodedef]") { ContentFeatures f; - f.name = "default:stone"; for (TileDef &tiledef : f.tiledef) tiledef.name = "default_stone.png"; @@ -56,12 +40,10 @@ void TestNodeDef::testContentFeaturesSerialization() std::ostringstream os(std::ios::binary); f.serialize(os, LATEST_PROTOCOL_VERSION); - // verbosestream<<"Test ContentFeatures size: "< Date: Wed, 28 Aug 2024 15:37:43 +0200 Subject: [PATCH 032/200] Menu docs: clarify that image paths must be escaped to correctly render on Windows (#15072) --- doc/menu_lua_api.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index 8f945052c..df14b859d 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -8,6 +8,15 @@ The main menu is defined as a formspec by Lua in `builtin/mainmenu/` Description of formspec language to show your menu is in `lua_api.md` +Images and 3D models +------ + +Directory delimiters change according to the OS (e.g. on Unix-like systems +is `/`, on Windows is `\`). When putting an image or a 3D model inside a formspec, +be sure to sanitize it first with `core.formspec_escape(img)`; otherwise, +any resource located in a subpath won't be displayed on OSs using `\` as delimiter. + + Callbacks --------- @@ -62,6 +71,12 @@ Functions Filesystem ---------- +To access specific subpaths, use `DIR_DELIM` as a directory delimiter instead +of manually putting one, as different OSs use different delimiters. E.g. +```lua +"my" .. DIR_DELIM .. "custom" .. DIR_DELIM .. "path" -- and not my/custom/path +``` + * `core.get_builtin_path()` * returns path to builtin root * `core.create_dir(absolute_path)` (possible in async calls) From 04f0a4a1c6e82d2a649790f9958a1b54426cd45e Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 24 Aug 2024 13:29:45 +0200 Subject: [PATCH 033/200] Fix MeshGrid::isMeshPos() `(1 + 1 + 0) % 2 = 0`, for example, so it had false positives. Only minimap generation uses this function. It did useless work. --- src/util/numeric.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/util/numeric.h b/src/util/numeric.h index 73b4fb5cd..cfe0317a1 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -179,7 +179,9 @@ struct MeshGrid { /// @brief Returns true if p is an origin of a cell in the grid. bool isMeshPos(v3s16 &p) const { - return ((p.X + p.Y + p.Z) % cell_size) == 0; + return p.X % cell_size == 0 + && p.Y % cell_size == 0 + && p.Z % cell_size == 0; } /// @brief Returns index of the given offset in a grid cell From 2e883189c102b07ad5b051878949ec77ef530bf0 Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 24 Aug 2024 13:32:39 +0200 Subject: [PATCH 034/200] Improve block bounds HUD feature * Use different material than selection box, so it doesn't break for non-default `node_highlighting` values. * Add `show_block_bounds_radius_near` setting. * Draw mesh chunk edges in a different color (red vs yellow). --- builtin/settingtypes.txt | 9 ++++-- src/client/hud.cpp | 68 +++++++++++++++++++++++++++++++--------- src/client/hud.h | 1 + src/defaultsettings.cpp | 1 + 4 files changed, 61 insertions(+), 18 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 8828fc1df..229d920d5 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -735,6 +735,12 @@ hud_scaling (HUD scaling) float 1.0 0.5 20 # Mods may still set a background. show_nametag_backgrounds (Show name tag backgrounds by default) bool true +# Whether to show the client debug info (has the same effect as hitting F5). +show_debug (Show debug info) bool false + +# Radius to use when the block bounds HUD feature is set to near blocks. +show_block_bounds_radius_near (Block bounds HUD radius) int 4 0 1000 + [**Chat] # Maximum number of recent chat messages to show @@ -2009,9 +2015,6 @@ client_unload_unused_data_timeout (Mapblock unload timeout) float 600.0 0.0 # Set to -1 for unlimited amount. client_mapblock_limit (Mapblock limit) int 7500 -1 2147483647 -# Whether to show the client debug info (has the same effect as hitting F5). -show_debug (Show debug info) bool false - # Maximum number of blocks that are simultaneously sent per client. # The maximum total count is calculated dynamically: # max_total = ceil((#clients + max_users) * per_client / 4) diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 962818b8f..3f9672f3d 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -97,6 +97,8 @@ Hud::Hud(Client *client, LocalPlayer *player, m_mode = HIGHLIGHT_BOX; } + // Initialize m_selection_material + m_selection_material.Lighting = false; if (g_settings->getBool("enable_shaders")) { @@ -118,6 +120,18 @@ Hud::Hud(Client *client, LocalPlayer *player, m_selection_material.MaterialType = video::EMT_SOLID; } + // Initialize m_block_bounds_material + m_block_bounds_material.Lighting = false; + if (g_settings->getBool("enable_shaders")) { + IShaderSource *shdrsrc = client->getShaderSource(); + auto shader_id = shdrsrc->getShader("default_shader", TILE_MATERIAL_ALPHA); + m_block_bounds_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material; + } else { + m_block_bounds_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + } + m_block_bounds_material.Thickness = + rangelim(g_settings->getS16("selectionbox_width"), 1, 5); + // Prepare mesh for compass drawing auto &b = m_rotation_mesh_buffer; b.Vertices.resize(4); @@ -934,33 +948,57 @@ void Hud::drawBlockBounds() } video::SMaterial old_material = driver->getMaterial2D(); - driver->setMaterial(m_selection_material); + driver->setMaterial(m_block_bounds_material); + + u16 mesh_chunk_size = std::max(1, g_settings->getU16("client_mesh_chunk")); v3s16 pos = player->getStandingNodePos(); - - v3s16 blockPos( + v3s16 block_pos( floorf((float) pos.X / MAP_BLOCKSIZE), floorf((float) pos.Y / MAP_BLOCKSIZE), floorf((float) pos.Z / MAP_BLOCKSIZE) ); - v3f offset = intToFloat(client->getCamera()->getOffset(), BS); + v3f cam_offset = intToFloat(client->getCamera()->getOffset(), BS); - s8 radius = m_block_bounds_mode == BLOCK_BOUNDS_NEAR ? 2 : 0; + v3f half_node = v3f(BS, BS, BS) / 2.0f; + v3f base_corner = intToFloat(block_pos * MAP_BLOCKSIZE, BS) - cam_offset - half_node; - v3f halfNode = v3f(BS, BS, BS) / 2.0f; + s16 radius = m_block_bounds_mode == BLOCK_BOUNDS_NEAR ? + rangelim(g_settings->getU16("show_block_bounds_radius_near"), 0, 1000) : 0; - for (s8 x = -radius; x <= radius; x++) - for (s8 y = -radius; y <= radius; y++) - for (s8 z = -radius; z <= radius; z++) { - v3s16 blockOffset(x, y, z); + for (s16 x = -radius; x <= radius + 1; x++) + for (s16 y = -radius; y <= radius + 1; y++) { + // Red for mesh chunk edges, yellow for other block edges. + auto choose_color = [&](s16 x_base, s16 y_base) { + // See also MeshGrid::isMeshPos(). + // If the block is mesh pos, it means it's at the (-,-,-) corner of + // the mesh. And we're drawing a (-,-) edge of this block. Hence, + // it is an edge of the mesh grid. + return (x + x_base) % mesh_chunk_size == 0 + && (y + y_base) % mesh_chunk_size == 0 ? + video::SColor(255, 255, 0, 0) : + video::SColor(255, 255, 255, 0); + }; - aabb3f box( - intToFloat((blockPos + blockOffset) * MAP_BLOCKSIZE, BS) - offset - halfNode, - intToFloat(((blockPos + blockOffset) * MAP_BLOCKSIZE) + (MAP_BLOCKSIZE - 1), BS) - offset + halfNode + v3f pmin = v3f(x, y, -radius) * MAP_BLOCKSIZE * BS; + v3f pmax = v3f(x, y, 1 + radius) * MAP_BLOCKSIZE * BS; + + driver->draw3DLine( + base_corner + v3f(pmin.X, pmin.Y, pmin.Z), + base_corner + v3f(pmax.X, pmax.Y, pmax.Z), + choose_color(block_pos.X, block_pos.Y) + ); + driver->draw3DLine( + base_corner + v3f(pmin.X, pmin.Z, pmin.Y), + base_corner + v3f(pmax.X, pmax.Z, pmax.Y), + choose_color(block_pos.X, block_pos.Z) + ); + driver->draw3DLine( + base_corner + v3f(pmin.Z, pmin.X, pmin.Y), + base_corner + v3f(pmax.Z, pmax.X, pmax.Y), + choose_color(block_pos.Y, block_pos.Z) ); - - driver->draw3DBox(box, video::SColor(255, 255, 0, 0)); } driver->setMaterial(old_material); diff --git a/src/client/hud.h b/src/client/hud.h index b2b5dd09c..3a9d27022 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -137,6 +137,7 @@ private: v3f m_selected_face_normal; video::SMaterial m_selection_material; + video::SMaterial m_block_bounds_material; scene::SMeshBuffer m_rotation_mesh_buffer; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 7d3273271..ccfb12971 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -302,6 +302,7 @@ void set_default_settings() settings->setDefault("enable_particles", "true"); settings->setDefault("arm_inertia", "true"); settings->setDefault("show_nametag_backgrounds", "true"); + settings->setDefault("show_block_bounds_radius_near", "4"); settings->setDefault("transparency_sorting_distance", "16"); settings->setDefault("enable_minimap", "true"); From 0f7ee126ded7164cba688d59348e8cc82b33d512 Mon Sep 17 00:00:00 2001 From: Desour Date: Sat, 24 Aug 2024 11:31:36 +0200 Subject: [PATCH 035/200] Fix transparency sorting and animation faraway check not using mesh chunk bounding sphere --- src/client/clientmap.cpp | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 45995c0ea..4c02879cc 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -767,15 +767,12 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) if (is_frustum_culled(mesh_sphere_center, mesh_sphere_radius)) continue; - v3f block_pos_r = intToFloat(block->getPosRelative() + MAP_BLOCKSIZE / 2, BS); - - float d = camera_position.getDistanceFrom(block_pos_r); - d = MYMAX(0,d - BLOCK_MAX_RADIUS); - // Mesh animation if (pass == scene::ESNRP_SOLID) { - // Pretty random but this should work somewhat nicely - bool faraway = d >= BS * 50; + // 50 nodes is pretty arbitrary but it should work somewhat nicely + float distance_sq = camera_position.getDistanceFromSQ(mesh_sphere_center); + bool faraway = distance_sq >= std::pow(BS * 50 + mesh_sphere_radius, 2.0f); + if (block_mesh->isAnimationForced() || !faraway || mesh_animate_count < (m_control.range_all ? 200 : 50)) { @@ -1305,27 +1302,29 @@ void ClientMap::updateTransparentMeshBuffers() ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG); u32 sorted_blocks = 0; u32 unsorted_blocks = 0; - f32 sorting_distance_sq = std::pow(m_cache_transparency_sorting_distance * BS, 2.0f); + f32 sorting_distance = m_cache_transparency_sorting_distance * BS; // Update the order of transparent mesh buffers in each mesh for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) { - MapBlock* block = it->second; - if (!block->mesh) + MapBlock *block = it->second; + MapBlockMesh *blockmesh = block->mesh; + if (!blockmesh) continue; if (m_needs_update_transparent_meshes || - block->mesh->getTransparentBuffers().size() == 0) { + blockmesh->getTransparentBuffers().size() == 0) { - v3s16 block_pos = block->getPos(); - v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS); - f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f); - if (distance <= sorting_distance_sq) { - block->mesh->updateTransparentBuffers(m_camera_position, block_pos); + v3f mesh_sphere_center = intToFloat(block->getPosRelative(), BS) + + blockmesh->getBoundingSphereCenter(); + f32 mesh_sphere_radius = blockmesh->getBoundingRadius(); + f32 distance_sq = m_camera_position.getDistanceFromSQ(mesh_sphere_center); + + if (distance_sq <= std::pow(sorting_distance + mesh_sphere_radius, 2.0f)) { + blockmesh->updateTransparentBuffers(m_camera_position, block->getPos()); ++sorted_blocks; - } - else { - block->mesh->consolidateTransparentBuffers(); + } else { + blockmesh->consolidateTransparentBuffers(); ++unsorted_blocks; } } From c52a4369eb89b2e0ecae02d021b55447245485aa Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 26 Aug 2024 23:51:01 +0200 Subject: [PATCH 036/200] Fix vertex count accounting in ClientMap --- src/client/clientmap.cpp | 23 ++++++++++------------- src/client/clientmap.h | 5 +++-- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 4c02879cc..eb5076e7f 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -841,10 +841,8 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) drawcall_count += draw_order.size(); for (auto &descriptor : draw_order) { - scene::IMeshBuffer *buf = descriptor.getBuffer(); - if (!descriptor.m_reuse_material) { - auto &material = buf->getMaterial(); + auto &material = descriptor.getMaterial(); // Apply filter settings material.forEachTexture([this] (auto &tex) { @@ -876,8 +874,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) m.setTranslation(block_wpos - offset); driver->setTransform(video::ETS_WORLD, m); - descriptor.draw(driver); - vertex_count += buf->getIndexCount(); + vertex_count += descriptor.draw(driver); } g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true)); @@ -1195,11 +1192,9 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, drawcall_count += draw_order.size(); for (auto &descriptor : draw_order) { - scene::IMeshBuffer *buf = descriptor.getBuffer(); - if (!descriptor.m_reuse_material) { // override some material properties - video::SMaterial local_material = buf->getMaterial(); + video::SMaterial local_material = descriptor.getMaterial(); local_material.MaterialType = material.MaterialType; // do not override culling if the original material renders both back // and front faces in solid mode (e.g. plantlike) @@ -1219,8 +1214,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, m.setTranslation(block_wpos - offset); driver->setTransform(video::ETS_WORLD, m); - descriptor.draw(driver); - vertex_count += buf->getIndexCount(); + vertex_count += descriptor.draw(driver); } // restore the driver material state @@ -1335,19 +1329,22 @@ void ClientMap::updateTransparentMeshBuffers() m_needs_update_transparent_meshes = false; } -scene::IMeshBuffer* ClientMap::DrawDescriptor::getBuffer() +video::SMaterial &ClientMap::DrawDescriptor::getMaterial() { - return m_use_partial_buffer ? m_partial_buffer->getBuffer() : m_buffer; + return (m_use_partial_buffer ? m_partial_buffer->getBuffer() : m_buffer)->getMaterial(); } -void ClientMap::DrawDescriptor::draw(video::IVideoDriver* driver) +u32 ClientMap::DrawDescriptor::draw(video::IVideoDriver* driver) { if (m_use_partial_buffer) { m_partial_buffer->beforeDraw(); driver->drawMeshBuffer(m_partial_buffer->getBuffer()); + auto count = m_partial_buffer->getBuffer()->getVertexCount(); m_partial_buffer->afterDraw(); + return count; } else { driver->drawMeshBuffer(m_buffer); + return m_buffer->getVertexCount(); } } diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 05c33d67c..6c424069a 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -162,8 +162,9 @@ private: m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true) {} - scene::IMeshBuffer* getBuffer(); - void draw(video::IVideoDriver* driver); + video::SMaterial &getMaterial(); + /// @return index count + u32 draw(video::IVideoDriver* driver); }; Client *m_client; From c00fed20b7e770b2ad360f8a4a3db31ce7b466f8 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 Aug 2024 09:14:31 +0200 Subject: [PATCH 037/200] Fix re-loading of settings in ClientMap --- src/client/clientmap.cpp | 55 ++++++++++++++++++++++------------------ src/client/clientmap.h | 2 +- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index eb5076e7f..cdad3146c 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -73,11 +73,23 @@ namespace { }; } +/* + ClientMap +*/ + static void on_settings_changed(const std::string &name, void *data) { - static_cast(data)->onSettingChanged(name); + static_cast(data)->onSettingChanged(name, false); } -// ClientMap + +static const std::string ClientMap_settings[] = { + "trilinear_filter", + "bilinear_filter", + "anisotropic_filter", + "transparency_sorting_distance", + "occlusion_culler", + "enable_raytraced_culling", +}; ClientMap::ClientMap( Client *client, @@ -102,37 +114,32 @@ ClientMap::ClientMap( m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000, BS*1000000,BS*1000000,BS*1000000); - /* TODO: Add a callback function so these can be updated when a setting - * changes. At this point in time it doesn't matter (e.g. /set - * is documented to change server settings only) - * - * TODO: Local caching of settings is not optimal and should at some stage - * be updated to use a global settings object for getting thse values - * (as opposed to the this local caching). This can be addressed in - * a later release. - */ - m_cache_trilinear_filter = g_settings->getBool("trilinear_filter"); - m_cache_bilinear_filter = g_settings->getBool("bilinear_filter"); - m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter"); - m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance"); - m_loops_occlusion_culler = g_settings->get("occlusion_culler") == "loops"; - g_settings->registerChangedCallback("occlusion_culler", on_settings_changed, this); - m_enable_raytraced_culling = g_settings->getBool("enable_raytraced_culling"); - g_settings->registerChangedCallback("enable_raytraced_culling", on_settings_changed, this); + for (const auto &name : ClientMap_settings) + g_settings->registerChangedCallback(name, on_settings_changed, this); + // load all settings at once + onSettingChanged("", true); } -void ClientMap::onSettingChanged(const std::string &name) +void ClientMap::onSettingChanged(std::string_view name, bool all) { - if (name == "occlusion_culler") + if (all || name == "trilinear_filter") + m_cache_trilinear_filter = g_settings->getBool("trilinear_filter"); + if (all || name == "bilinear_filter") + m_cache_bilinear_filter = g_settings->getBool("bilinear_filter"); + if (all || name == "anisotropic_filter") + m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter"); + if (all || name == "transparency_sorting_distance") + m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance"); + if (all || name == "occlusion_culler") m_loops_occlusion_culler = g_settings->get("occlusion_culler") == "loops"; - if (name == "enable_raytraced_culling") + if (all || name == "enable_raytraced_culling") m_enable_raytraced_culling = g_settings->getBool("enable_raytraced_culling"); } ClientMap::~ClientMap() { - g_settings->deregisterChangedCallback("occlusion_culler", on_settings_changed, this); - g_settings->deregisterChangedCallback("enable_raytraced_culling", on_settings_changed, this); + for (const auto &name : ClientMap_settings) + g_settings->deregisterChangedCallback(name, on_settings_changed, this); } void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset, video::SColor light_color) diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 6c424069a..7456902c8 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -112,7 +112,7 @@ public: f32 getWantedRange() const { return m_control.wanted_range; } f32 getCameraFov() const { return m_camera_fov; } - void onSettingChanged(const std::string &name); + void onSettingChanged(std::string_view name, bool all); protected: // use drop() instead From 39970fed38ef2f7353a4cd2ba9dc1e4c6a9baa22 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 Aug 2024 10:03:13 +0200 Subject: [PATCH 038/200] Consolidate transparent buffers lazily --- src/client/mapblock_mesh.cpp | 5 +++++ src/client/mapblock_mesh.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 9b5612148..04c50ad99 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -906,6 +906,7 @@ void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos) m_bsp_tree.traverse(rel_camera_pos, triangle_refs); // arrange index sequences into partial buffers + m_transparent_buffers_consolidated = false; m_transparent_buffers.clear(); scene::SMeshBuffer *current_buffer = nullptr; @@ -930,6 +931,8 @@ void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos) void MapBlockMesh::consolidateTransparentBuffers() { + if (m_transparent_buffers_consolidated) + return; m_transparent_buffers.clear(); scene::SMeshBuffer *current_buffer = nullptr; @@ -952,6 +955,8 @@ void MapBlockMesh::consolidateTransparentBuffers() if (!current_strain.empty()) { this->m_transparent_buffers.emplace_back(current_buffer, std::move(current_strain)); } + + m_transparent_buffers_consolidated = true; } video::SColor encode_light(u16 light, u8 emissive_light) diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 7cd368762..6ddd988aa 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -282,6 +282,8 @@ private: MapBlockBspTree m_bsp_tree; // Ordered list of references to parts of transparent buffers to draw std::vector m_transparent_buffers; + // Is m_transparent_buffers currently in consolidated form? + bool m_transparent_buffers_consolidated = false; }; /*! From fa4529b4f166a8ca160520c8867320fad7b5357b Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 Aug 2024 12:09:08 +0200 Subject: [PATCH 039/200] Keep stats on hw buffer uploads --- irr/include/IVideoDriver.h | 23 +++++++++++------------ irr/src/CFPSCounter.cpp | 35 ++--------------------------------- irr/src/CFPSCounter.h | 18 ++---------------- irr/src/CNullDriver.cpp | 17 +++++++---------- irr/src/CNullDriver.h | 14 ++++++++------ irr/src/COpenGLDriver.cpp | 4 ++++ irr/src/OpenGL/Driver.cpp | 25 ++++++++++++++----------- src/client/game.cpp | 5 +++++ 8 files changed, 53 insertions(+), 88 deletions(-) diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index 888e4a9f7..7b15fdd1b 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -114,6 +114,15 @@ const c8 *const FogTypeNames[] = { 0, }; +struct SFrameStats { + //! Count of primitives drawn + u32 PrimitivesDrawn = 0; + //! Number of hardware buffers uploaded (new or updated) + u32 HWBuffersUploaded = 0; + //! Sum of uploaded hardware buffer size + u32 HWBuffersUploadedSize = 0; +}; + //! Interface to driver which is able to perform 2d and 3d graphics functions. /** This interface is one of the most important interfaces of the Irrlicht Engine: All rendering and texture manipulation is done with @@ -194,12 +203,6 @@ public: */ virtual const io::IAttributes &getDriverAttributes() const = 0; - //! Check if the driver was recently reset. - /** For d3d devices you will need to recreate the RTTs if the - driver was reset. Should be queried right after beginScene(). - */ - virtual bool checkDriverReset() = 0; - //! Sets transformation matrices. /** \param state Transformation type to be set, e.g. view, world, or projection. @@ -855,12 +858,8 @@ public: \return Approximate amount of frames per second drawn. */ virtual s32 getFPS() const = 0; - //! Returns amount of primitives (mostly triangles) which were drawn in the last frame. - /** Together with getFPS() very useful method for statistics. - \param mode Defines if the primitives drawn are accumulated or - counted per frame. - \return Amount of primitives drawn in the last frame. */ - virtual u32 getPrimitiveCountDrawn(u32 mode = 0) const = 0; + //! Return some statistics about the last frame + virtual SFrameStats getFrameStats() const = 0; //! Gets name of this video driver. /** \return Returns the name of the video driver, e.g. in case diff --git a/irr/src/CFPSCounter.cpp b/irr/src/CFPSCounter.cpp index 6717df5c9..0542fc5cb 100644 --- a/irr/src/CFPSCounter.cpp +++ b/irr/src/CFPSCounter.cpp @@ -11,53 +11,22 @@ namespace video { CFPSCounter::CFPSCounter() : - FPS(60), Primitive(0), StartTime(0), FramesCounted(0), - PrimitivesCounted(0), PrimitiveAverage(0), PrimitiveTotal(0) + FPS(0), StartTime(0), FramesCounted(0) { } -//! returns current fps -s32 CFPSCounter::getFPS() const -{ - return FPS; -} - -//! returns current primitive count -u32 CFPSCounter::getPrimitive() const -{ - return Primitive; -} - -//! returns average primitive count of last period -u32 CFPSCounter::getPrimitiveAverage() const -{ - return PrimitiveAverage; -} - -//! returns accumulated primitive count since start -u32 CFPSCounter::getPrimitiveTotal() const -{ - return PrimitiveTotal; -} - //! to be called every frame -void CFPSCounter::registerFrame(u32 now, u32 primitivesDrawn) +void CFPSCounter::registerFrame(u32 now) { ++FramesCounted; - PrimitiveTotal += primitivesDrawn; - PrimitivesCounted += primitivesDrawn; - Primitive = primitivesDrawn; const u32 milliseconds = now - StartTime; - if (milliseconds >= 1500) { const f32 invMilli = core::reciprocal((f32)milliseconds); FPS = core::ceil32((1000 * FramesCounted) * invMilli); - PrimitiveAverage = core::ceil32((1000 * PrimitivesCounted) * invMilli); FramesCounted = 0; - PrimitivesCounted = 0; StartTime = now; } } diff --git a/irr/src/CFPSCounter.h b/irr/src/CFPSCounter.h index e73afb049..692aef6bd 100644 --- a/irr/src/CFPSCounter.h +++ b/irr/src/CFPSCounter.h @@ -17,29 +17,15 @@ public: CFPSCounter(); //! returns current fps - s32 getFPS() const; - - //! returns primitive count - u32 getPrimitive() const; - - //! returns average primitive count of last period - u32 getPrimitiveAverage() const; - - //! returns accumulated primitive count since start - u32 getPrimitiveTotal() const; + s32 getFPS() const { return FPS; } //! to be called every frame - void registerFrame(u32 now, u32 primitive); + void registerFrame(u32 now); private: s32 FPS; - u32 Primitive; u32 StartTime; - u32 FramesCounted; - u32 PrimitivesCounted; - u32 PrimitiveAverage; - u32 PrimitiveTotal; }; } // end namespace video diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index c1cecbe8c..054267711 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -53,7 +53,7 @@ public: //! constructor CNullDriver::CNullDriver(io::IFileSystem *io, const core::dimension2d &screenSize) : SharedRenderTarget(0), CurrentRenderTarget(0), CurrentRenderTargetSize(0, 0), FileSystem(io), MeshManipulator(0), - ViewPort(0, 0, 0, 0), ScreenSize(screenSize), PrimitivesDrawn(0), MinVertexCountForVBO(500), + ViewPort(0, 0, 0, 0), ScreenSize(screenSize), MinVertexCountForVBO(500), TextureCreationFlags(0), OverrideMaterial2DEnabled(false), AllowZWriteOnTransparent(false) { #ifdef _DEBUG @@ -222,13 +222,13 @@ void CNullDriver::deleteAllTextures() bool CNullDriver::beginScene(u16 clearFlag, SColor clearColor, f32 clearDepth, u8 clearStencil, const SExposedVideoData &videoData, core::rect *sourceRect) { - PrimitivesDrawn = 0; + FrameStats = {}; return true; } bool CNullDriver::endScene() { - FPSCounter.registerFrame(os::Timer::getRealTime(), PrimitivesDrawn); + FPSCounter.registerFrame(os::Timer::getRealTime()); updateAllHardwareBuffers(); updateAllOcclusionQueries(); return true; @@ -605,7 +605,7 @@ void CNullDriver::drawVertexPrimitiveList(const void *vertices, u32 vertexCount, { if ((iType == EIT_16BIT) && (vertexCount > 65536)) os::Printer::log("Too many vertices for 16bit index type, render artifacts may occur."); - PrimitivesDrawn += primitiveCount; + FrameStats.PrimitivesDrawn += primitiveCount; } //! draws a vertex primitive list in 2d @@ -613,7 +613,7 @@ void CNullDriver::draw2DVertexPrimitiveList(const void *vertices, u32 vertexCoun { if ((iType == EIT_16BIT) && (vertexCount > 65536)) os::Printer::log("Too many vertices for 16bit index type, render artifacts may occur."); - PrimitivesDrawn += primitiveCount; + FrameStats.PrimitivesDrawn += primitiveCount; } //! Draws a 3d line. @@ -743,12 +743,9 @@ s32 CNullDriver::getFPS() const return FPSCounter.getFPS(); } -//! returns amount of primitives (mostly triangles) were drawn in the last frame. -//! very useful method for statistics. -u32 CNullDriver::getPrimitiveCountDrawn(u32 param) const +SFrameStats CNullDriver::getFrameStats() const { - return (0 == param) ? FPSCounter.getPrimitive() : (1 == param) ? FPSCounter.getPrimitiveAverage() - : FPSCounter.getPrimitiveTotal(); + return FrameStats; } //! Sets the dynamic ambient light color. The default color is diff --git a/irr/src/CNullDriver.h b/irr/src/CNullDriver.h index 30911b7a6..302108a1b 100644 --- a/irr/src/CNullDriver.h +++ b/irr/src/CNullDriver.h @@ -195,9 +195,7 @@ public: // get current frames per second value s32 getFPS() const override; - //! returns amount of primitives (mostly triangles) were drawn in the last frame. - //! very useful method for statistics. - u32 getPrimitiveCountDrawn(u32 param = 0) const override; + SFrameStats getFrameStats() const override; //! \return Returns the name of the video driver. Example: In case of the DIRECT3D8 //! driver, it would return "Direct3D8.1". @@ -538,8 +536,6 @@ public: virtual void convertColor(const void *sP, ECOLOR_FORMAT sF, s32 sN, void *dP, ECOLOR_FORMAT dF) const override; - bool checkDriverReset() override { return false; } - protected: //! deletes all textures void deleteAllTextures(); @@ -570,6 +566,12 @@ protected: // prints renderer version void printVersion(); + inline void accountHWBufferUpload(u32 size) + { + FrameStats.HWBuffersUploaded++; + FrameStats.HWBuffersUploadedSize += size; + } + inline bool getWriteZBuffer(const SMaterial &material) const { switch (material.ZWriteEnable) { @@ -696,8 +698,8 @@ protected: core::matrix4 TransformationMatrix; CFPSCounter FPSCounter; + SFrameStats FrameStats; - u32 PrimitivesDrawn; u32 MinVertexCountForVBO; u32 TextureCreationFlags; diff --git a/irr/src/COpenGLDriver.cpp b/irr/src/COpenGLDriver.cpp index 1c6342090..875534a4e 100644 --- a/irr/src/COpenGLDriver.cpp +++ b/irr/src/COpenGLDriver.cpp @@ -266,6 +266,8 @@ bool COpenGLDriver::updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) const E_VERTEX_TYPE vType = mb->getVertexType(); const u32 vertexSize = getVertexPitchFromType(vType); + accountHWBufferUpload(vertexSize * vertexCount); + const c8 *vbuf = static_cast(vertices); core::array buffer; if (!FeatureAvailable[IRR_ARB_vertex_array_bgra] && !FeatureAvailable[IRR_EXT_vertex_array_bgra]) { @@ -367,6 +369,8 @@ bool COpenGLDriver::updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) } } + accountHWBufferUpload(indexCount * indexSize); + // get or create buffer bool newBuffer = false; if (!HWBuffer->vbo_indicesID) { diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 1defa9abc..f681a3de9 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -255,10 +255,7 @@ bool COpenGL3DriverBase::genericDriverInit(const core::dimension2d &screenS DriverAttributes->setAttribute("MaxTextures", (s32)Feature.MaxTextureUnits); DriverAttributes->setAttribute("MaxSupportedTextures", (s32)Feature.MaxTextureUnits); - // DriverAttributes->setAttribute("MaxLights", MaxLights); DriverAttributes->setAttribute("MaxAnisotropy", MaxAnisotropy); - // DriverAttributes->setAttribute("MaxAuxBuffers", MaxAuxBuffers); - // DriverAttributes->setAttribute("MaxMultipleRenderTargets", MaxMultipleRenderTargets); DriverAttributes->setAttribute("MaxIndices", (s32)MaxIndices); DriverAttributes->setAttribute("MaxTextureSize", (s32)MaxTextureSize); DriverAttributes->setAttribute("MaxTextureLODBias", MaxTextureLODBias); @@ -493,6 +490,7 @@ bool COpenGL3DriverBase::updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuff const void *buffer = vertices; size_t bufferSize = vertexSize * vertexCount; + accountHWBufferUpload(bufferSize); // get or create buffer bool newBuffer = false; @@ -551,6 +549,9 @@ bool COpenGL3DriverBase::updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffe } } + const size_t bufferSize = indexCount * indexSize; + accountHWBufferUpload(bufferSize); + // get or create buffer bool newBuffer = false; if (!HWBuffer->vbo_indicesID) { @@ -558,7 +559,7 @@ bool COpenGL3DriverBase::updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffe if (!HWBuffer->vbo_indicesID) return false; newBuffer = true; - } else if (HWBuffer->vbo_indicesSize < indexCount * indexSize) { + } else if (HWBuffer->vbo_indicesSize < bufferSize) { newBuffer = true; } @@ -566,16 +567,16 @@ bool COpenGL3DriverBase::updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffe // copy data to graphics card if (!newBuffer) - GL.BufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexCount * indexSize, indices); + GL.BufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, bufferSize, indices); else { - HWBuffer->vbo_indicesSize = indexCount * indexSize; + HWBuffer->vbo_indicesSize = bufferSize; GLenum usage = GL_STATIC_DRAW; if (HWBuffer->Mapped_Index == scene::EHM_STREAM) usage = GL_STREAM_DRAW; else if (HWBuffer->Mapped_Index == scene::EHM_DYNAMIC) usage = GL_DYNAMIC_DRAW; - GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * indexSize, indices, usage); + GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, bufferSize, indices, usage); } GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -589,22 +590,24 @@ bool COpenGL3DriverBase::updateHardwareBuffer(SHWBufferLink *HWBuffer) if (!HWBuffer) return false; + auto *b = static_cast(HWBuffer); + if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) { - if (HWBuffer->ChangedID_Vertex != HWBuffer->MeshBuffer->getChangedID_Vertex() || !static_cast(HWBuffer)->vbo_verticesID) { + if (HWBuffer->ChangedID_Vertex != HWBuffer->MeshBuffer->getChangedID_Vertex() || !b->vbo_verticesID) { HWBuffer->ChangedID_Vertex = HWBuffer->MeshBuffer->getChangedID_Vertex(); - if (!updateVertexHardwareBuffer(static_cast(HWBuffer))) + if (!updateVertexHardwareBuffer(b)) return false; } } if (HWBuffer->Mapped_Index != scene::EHM_NEVER) { - if (HWBuffer->ChangedID_Index != HWBuffer->MeshBuffer->getChangedID_Index() || !static_cast(HWBuffer)->vbo_indicesID) { + if (HWBuffer->ChangedID_Index != HWBuffer->MeshBuffer->getChangedID_Index() || !b->vbo_indicesID) { HWBuffer->ChangedID_Index = HWBuffer->MeshBuffer->getChangedID_Index(); - if (!updateIndexHardwareBuffer((SHWBufferLink_opengl *)HWBuffer)) + if (!updateIndexHardwareBuffer(b)) return false; } } diff --git a/src/client/game.cpp b/src/client/game.cpp index 41c7972cb..1267b32a9 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1953,6 +1953,11 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time); g_profiler->graphSet("FPS", 1.0f / dtime); + + auto stats2 = driver->getFrameStats(); + g_profiler->avg("Irr: primitives drawn", stats2.PrimitivesDrawn); + g_profiler->avg("Irr: buffers uploaded", stats2.HWBuffersUploaded); + g_profiler->avg("Irr: buffers uploaded (bytes)", stats2.HWBuffersUploadedSize); } void Game::updateStats(RunStats *stats, const FpsControl &draw_times, From 19a58745c9db8b984d72f6634a1307e2f2d39c00 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 Aug 2024 13:51:02 +0200 Subject: [PATCH 040/200] Avoid copies when working with EnrichedString --- src/irrlicht_changes/CGUITTFont.cpp | 7 +++-- src/irrlicht_changes/static_text.h | 4 +-- src/util/enriched_string.cpp | 24 +++++----------- src/util/enriched_string.h | 23 +++++++++------ src/util/string.cpp | 43 ++++++++++++++--------------- src/util/string.h | 6 ++-- 6 files changed, 53 insertions(+), 54 deletions(-) diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index 40d2bb405..4f5f52b4e 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -513,12 +513,15 @@ void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hintin void CGUITTFont::draw(const core::stringw& text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) { - draw(EnrichedString(std::wstring(text.c_str()), color), position, hcenter, vcenter, clip); + // Allow colors to work for strings that have passed through irrlicht by catching + // them here and converting them to enriched just before drawing. + EnrichedString s(text.c_str(), color); + draw(s, position, hcenter, vcenter, clip); } void CGUITTFont::draw(const EnrichedString &text, const core::rect& position, bool hcenter, bool vcenter, const core::rect* clip) { - const std::vector &colors = text.getColors(); + const auto &colors = text.getColors(); if (!Driver) return; diff --git a/src/irrlicht_changes/static_text.h b/src/irrlicht_changes/static_text.h index 636760f6c..15a976c74 100644 --- a/src/irrlicht_changes/static_text.h +++ b/src/irrlicht_changes/static_text.h @@ -61,7 +61,7 @@ namespace gui static irr::gui::IGUIStaticText *add( irr::gui::IGUIEnvironment *guienv, - const wchar_t *text, + std::wstring_view text, const core::rect< s32 > &rectangle, bool border = false, bool wordWrap = true, @@ -204,7 +204,7 @@ inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedS } } -inline void setStaticText(irr::gui::IGUIStaticText *static_text, const wchar_t *text) +inline void setStaticText(irr::gui::IGUIStaticText *static_text, std::wstring_view text) { setStaticText(static_text, EnrichedString(text, static_text->getOverrideColor())); } diff --git a/src/util/enriched_string.cpp b/src/util/enriched_string.cpp index 04c5ef806..09baeac29 100644 --- a/src/util/enriched_string.cpp +++ b/src/util/enriched_string.cpp @@ -29,7 +29,7 @@ EnrichedString::EnrichedString() clear(); } -EnrichedString::EnrichedString(const std::wstring &string, +EnrichedString::EnrichedString(std::wstring_view string, const std::vector &colors) { clear(); @@ -37,18 +37,12 @@ EnrichedString::EnrichedString(const std::wstring &string, m_colors = colors; } -EnrichedString::EnrichedString(const std::wstring &s, const SColor &color) +EnrichedString::EnrichedString(std::wstring_view s, const SColor &color) { clear(); addAtEnd(translate_string(s), color); } -EnrichedString::EnrichedString(const wchar_t *str, const SColor &color) -{ - clear(); - addAtEnd(translate_string(std::wstring(str)), color); -} - void EnrichedString::clear() { m_string.clear(); @@ -59,19 +53,20 @@ void EnrichedString::clear() m_background = irr::video::SColor(0, 0, 0, 0); } -EnrichedString &EnrichedString::operator=(const wchar_t *str) +EnrichedString &EnrichedString::operator=(std::wstring_view str) { clear(); - addAtEnd(translate_string(std::wstring(str)), m_default_color); + addAtEnd(translate_string(str), m_default_color); return *this; } -void EnrichedString::addAtEnd(const std::wstring &s, SColor initial_color) +void EnrichedString::addAtEnd(std::wstring_view s, SColor initial_color) { SColor color(initial_color); bool use_default = (m_default_length == m_string.size() && color == m_default_color); + m_string.reserve(m_string.size() + s.size()); m_colors.reserve(m_colors.size() + s.size()); size_t i = 0; @@ -142,7 +137,7 @@ void EnrichedString::addCharNoColor(wchar_t c) if (m_colors.empty()) { m_colors.emplace_back(m_default_color); } else { - m_colors.push_back(m_colors[m_colors.size() - 1]); + m_colors.push_back(m_colors.back()); } } @@ -203,11 +198,6 @@ EnrichedString EnrichedString::substr(size_t pos, size_t len) const return str; } -const wchar_t *EnrichedString::c_str() const -{ - return m_string.c_str(); -} - const std::vector &EnrichedString::getColors() const { return m_colors; diff --git a/src/util/enriched_string.h b/src/util/enriched_string.h index 3d19eaed5..18fd967bc 100644 --- a/src/util/enriched_string.h +++ b/src/util/enriched_string.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include +#include #include #include @@ -28,17 +29,15 @@ using namespace irr; class EnrichedString { public: EnrichedString(); - EnrichedString(const std::wstring &s, + EnrichedString(std::wstring_view s, const video::SColor &color = video::SColor(255, 255, 255, 255)); - EnrichedString(const wchar_t *str, - const video::SColor &color = video::SColor(255, 255, 255, 255)); - EnrichedString(const std::wstring &string, + EnrichedString(std::wstring_view string, const std::vector &colors); - EnrichedString &operator=(const wchar_t *str); + EnrichedString &operator=(std::wstring_view s); void clear(); - void addAtEnd(const std::wstring &s, video::SColor color); + void addAtEnd(std::wstring_view s, video::SColor color); // Adds the character source[i] at the end. // An EnrichedString should always be able to be copied @@ -51,9 +50,18 @@ public: EnrichedString getNextLine(size_t *pos) const; EnrichedString substr(size_t pos = 0, size_t len = std::string::npos) const; + EnrichedString operator+(const EnrichedString &other) const; void operator+=(const EnrichedString &other); - const wchar_t *c_str() const; + void operator+=(std::wstring_view other) + { + *this += EnrichedString(other); + } + + const wchar_t *c_str() const + { + return getString().c_str(); + } const std::vector &getColors() const; const std::wstring &getString() const; @@ -106,6 +114,5 @@ private: video::SColor m_default_color; video::SColor m_background; // This variable defines the length of the default-colored text. - // Change this to a std::vector if an "end coloring" tag is wanted. size_t m_default_length = 0; }; diff --git a/src/util/string.cpp b/src/util/string.cpp index 73d1d6907..74a360266 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -670,23 +670,26 @@ std::string wrap_rows(std::string_view from, unsigned row_len, bool has_color_co * before filling it again. */ -static void translate_all(const std::wstring &s, size_t &i, +static void translate_all(std::wstring_view s, size_t &i, Translations *translations, std::wstring &res); -static void translate_string(const std::wstring &s, Translations *translations, +static void translate_string(std::wstring_view s, Translations *translations, const std::wstring &textdomain, size_t &i, std::wstring &res) { - std::wostringstream output; std::vector args; int arg_number = 1; + + // Re-assemble the template. + std::wstring output; + output.reserve(s.length()); while (i < s.length()) { // Not an escape sequence: just add the character. if (s[i] != '\x1b') { - output.put(s[i]); + output += s[i]; // The character is a literal '@'; add it twice // so that it is not mistaken for an argument. if (s[i] == L'@') - output.put(L'@'); + output += L'@'; ++i; continue; } @@ -733,12 +736,12 @@ static void translate_string(const std::wstring &s, Translations *translations, args.push_back(arg); continue; } - output.put(L'@'); - output << arg_number; + output += L'@'; + output += std::to_wstring(arg_number); ++arg_number; std::wstring arg; translate_all(s, i, translations, arg); - args.push_back(arg); + args.push_back(std::move(arg)); } else { // This is an escape sequence *inside* the template string to translate itself. // This should not happen, show an error message. @@ -747,21 +750,18 @@ static void translate_string(const std::wstring &s, Translations *translations, } } - std::wstring toutput; // Translate the template. - if (translations != nullptr) - toutput = translations->getTranslation( - textdomain, output.str()); - else - toutput = output.str(); + const std::wstring &toutput = translations ? + translations->getTranslation(textdomain, output) : output; // Put back the arguments in the translated template. - std::wostringstream result; size_t j = 0; + res.clear(); + res.reserve(toutput.length()); while (j < toutput.length()) { // Normal character, add it to output and continue. if (toutput[j] != L'@' || j == toutput.length() - 1) { - result.put(toutput[j]); + res += toutput[j]; ++j; continue; } @@ -769,7 +769,7 @@ static void translate_string(const std::wstring &s, Translations *translations, ++j; // Literal escape for '@'. if (toutput[j] == L'@') { - result.put(L'@'); + res += L'@'; ++j; continue; } @@ -778,16 +778,15 @@ static void translate_string(const std::wstring &s, Translations *translations, int arg_index = toutput[j] - L'1'; ++j; if (0 <= arg_index && (size_t)arg_index < args.size()) { - result << args[arg_index]; + res += args[arg_index]; } else { // This is not allowed: show an error message errorstream << "Ignoring out-of-bounds argument escape sequence in translation" << std::endl; } } - res = result.str(); } -static void translate_all(const std::wstring &s, size_t &i, +static void translate_all(std::wstring_view s, size_t &i, Translations *translations, std::wstring &res) { res.clear(); @@ -849,7 +848,7 @@ static void translate_all(const std::wstring &s, size_t &i, } // Translate string server side -std::wstring translate_string(const std::wstring &s, Translations *translations) +std::wstring translate_string(std::wstring_view s, Translations *translations) { size_t i = 0; std::wstring res; @@ -858,7 +857,7 @@ std::wstring translate_string(const std::wstring &s, Translations *translations) } // Translate string client side -std::wstring translate_string(const std::wstring &s) +std::wstring translate_string(std::wstring_view s) { #ifdef SERVER return translate_string(s, nullptr); diff --git a/src/util/string.h b/src/util/string.h index ad3d09818..aae1167b6 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -631,11 +631,11 @@ std::vector > split(const std::basic_string &s, T delim) return tokens; } -std::wstring translate_string(const std::wstring &s, Translations *translations); +std::wstring translate_string(std::wstring_view s, Translations *translations); -std::wstring translate_string(const std::wstring &s); +std::wstring translate_string(std::wstring_view s); -inline std::wstring unescape_translate(const std::wstring &s) { +inline std::wstring unescape_translate(std::wstring_view s) { return unescape_enriched(translate_string(s)); } From 1298d6c0206a964f4f5cb5a94b8463340c7dbbf4 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 28 Aug 2024 14:56:10 +0200 Subject: [PATCH 041/200] Fix VBO hint for transparent block parts --- src/client/mapblock_mesh.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 04c50ad99..0638c34c9 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -783,12 +783,15 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs } if (mesh) { - // Use VBO for mesh (this just would set this for ever buffer) + // Use VBO for mesh (this just would set this for every buffer) mesh->setHardwareMappingHint(scene::EHM_STATIC); } } - //std::cout<<"added "<setHardwareMappingHint(scene::EHM_STREAM, scene::EBT_INDEX); + m_bsp_tree.buildTree(&m_transparent_triangles, data->side_length); // Check if animation is required for this mesh From bf4d31227bc08c1940384e8cd00d25609c7ed674 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 28 Aug 2024 20:44:42 +0200 Subject: [PATCH 042/200] Delete OpenGL ES 1.0 driver (#15067) --- builtin/settingtypes.txt | 3 +- irr/README.md | 1 - irr/include/EDriverTypes.h | 3 - irr/src/CEGLManager.cpp | 6 - irr/src/CIrrDeviceLinux.cpp | 22 +- irr/src/CIrrDeviceOSX.mm | 3 +- irr/src/CIrrDeviceSDL.cpp | 8 +- irr/src/CIrrDeviceWin32.cpp | 21 +- irr/src/CMakeLists.txt | 31 - irr/src/COGLESCommon.h | 122 -- irr/src/COGLESDriver.cpp | 2346 ---------------------------- irr/src/COGLESDriver.h | 317 ---- irr/src/COGLESExtensionHandler.cpp | 89 -- irr/src/COGLESExtensionHandler.h | 189 --- irr/src/COGLESMaterialRenderer.h | 286 ---- irr/src/COpenGLCoreCacheHandler.h | 8 +- irr/src/COpenGLCoreTexture.h | 3 +- irr/src/Irrlicht.cpp | 4 - src/client/renderingengine.cpp | 2 - 19 files changed, 11 insertions(+), 3453 deletions(-) delete mode 100644 irr/src/COGLESCommon.h delete mode 100644 irr/src/COGLESDriver.cpp delete mode 100644 irr/src/COGLESDriver.h delete mode 100644 irr/src/COGLESExtensionHandler.cpp delete mode 100644 irr/src/COGLESExtensionHandler.h delete mode 100644 irr/src/COGLESMaterialRenderer.h diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 229d920d5..001eb7288 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1851,8 +1851,7 @@ shader_path (Shader path) path # The rendering back-end. # Note: A restart is required after changing this! # OpenGL is the default for desktop, and OGLES2 for Android. -# Shaders are supported by everything but OGLES1. -video_driver (Video driver) enum ,opengl,opengl3,ogles1,ogles2 +video_driver (Video driver) enum ,opengl,opengl3,ogles2 # Distance in nodes at which transparency depth sorting is enabled # Use this to limit the performance impact of transparency depth sorting diff --git a/irr/README.md b/irr/README.md index 640b82c6f..50e7338a5 100644 --- a/irr/README.md +++ b/irr/README.md @@ -20,7 +20,6 @@ The following libraries are required to be installed: Aside from standard search options (`ZLIB_INCLUDE_DIR`, `ZLIB_LIBRARY`, ...) the following options are available: * `ENABLE_OPENGL` - Enable OpenGL driver * `ENABLE_OPENGL3` (default: `OFF`) - Enable OpenGL 3+ driver -* `ENABLE_GLES1` - Enable OpenGL ES driver, legacy * `ENABLE_GLES2` - Enable OpenGL ES 2+ driver * `USE_SDL2` (default: platform-dependent, usually `ON`) - Use SDL2 instead of older native device code diff --git a/irr/include/EDriverTypes.h b/irr/include/EDriverTypes.h index 33f987889..f19e65ace 100644 --- a/irr/include/EDriverTypes.h +++ b/irr/include/EDriverTypes.h @@ -24,9 +24,6 @@ enum E_DRIVER_TYPE primitives. */ EDT_OPENGL, - //! OpenGL-ES 1.x driver, for embedded and mobile systems - EDT_OGLES1, - //! OpenGL-ES 2.x driver, for embedded and mobile systems /** Supports shaders etc. */ EDT_OGLES2, diff --git a/irr/src/CEGLManager.cpp b/irr/src/CEGLManager.cpp index ffe7a44cf..daaa64fdf 100644 --- a/irr/src/CEGLManager.cpp +++ b/irr/src/CEGLManager.cpp @@ -152,9 +152,6 @@ EGLConfig CEGLManager::chooseConfig(EConfigStyle confStyle) // Find proper OpenGL BIT. EGLint eglOpenGLBIT = 0; switch (Params.DriverType) { - case EDT_OGLES1: - eglOpenGLBIT = EGL_OPENGL_ES_BIT; - break; case EDT_OGLES2: case EDT_WEBGL1: eglOpenGLBIT = EGL_OPENGL_ES2_BIT; @@ -459,9 +456,6 @@ bool CEGLManager::generateContext() EGLint OpenGLESVersion = 0; switch (Params.DriverType) { - case EDT_OGLES1: - OpenGLESVersion = 1; - break; case EDT_OGLES2: case EDT_WEBGL1: OpenGLESVersion = 2; diff --git a/irr/src/CIrrDeviceLinux.cpp b/irr/src/CIrrDeviceLinux.cpp index 5491d2037..5e3afae36 100644 --- a/irr/src/CIrrDeviceLinux.cpp +++ b/irr/src/CIrrDeviceLinux.cpp @@ -33,7 +33,7 @@ #include #endif -#if defined(_IRR_COMPILE_WITH_OGLES1_) || defined(_IRR_COMPILE_WITH_OGLES2_) +#if defined(_IRR_COMPILE_WITH_OGLES2_) #include "CEGLManager.h" #endif @@ -76,10 +76,6 @@ namespace video IVideoDriver *createOpenGLDriver(const irr::SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager); #endif -#ifdef _IRR_COMPILE_WITH_OGLES1_ -IVideoDriver *createOGLES1Driver(const irr::SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager); -#endif - #ifdef _IRR_COMPILE_WITH_OGLES2_ IVideoDriver *createOGLES2Driver(const irr::SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager); #endif @@ -554,22 +550,6 @@ void CIrrDeviceLinux::createDriver() } #else os::Printer::log("No OpenGL support compiled in.", ELL_ERROR); -#endif - break; - case video::EDT_OGLES1: -#ifdef _IRR_COMPILE_WITH_OGLES1_ - { - video::SExposedVideoData data; - data.OpenGLLinux.X11Window = XWindow; - data.OpenGLLinux.X11Display = XDisplay; - - ContextManager = new video::CEGLManager(); - ContextManager->initialize(CreationParams, data); - - VideoDriver = video::createOGLES1Driver(CreationParams, FileSystem, ContextManager); - } -#else - os::Printer::log("No OpenGL-ES1 support compiled in.", ELL_ERROR); #endif break; case video::EDT_OGLES2: diff --git a/irr/src/CIrrDeviceOSX.mm b/irr/src/CIrrDeviceOSX.mm index 67c0ce05c..e335085e4 100644 --- a/irr/src/CIrrDeviceOSX.mm +++ b/irr/src/CIrrDeviceOSX.mm @@ -720,9 +720,8 @@ void CIrrDeviceMacOSX::createDriver() #endif break; - case video::EDT_OGLES1: case video::EDT_OGLES2: - os::Printer::log("This driver is not available in OSX. Try OpenGL or Software renderer.", ELL_ERROR); + os::Printer::log("This driver is not available on OSX.", ELL_ERROR); break; case video::EDT_NULL: diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index ffbd81950..58fba4f25 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -594,18 +594,14 @@ bool CIrrDeviceSDL::createWindowWithContext() SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); break; - case video::EDT_OGLES1: - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - break; case video::EDT_OGLES2: case video::EDT_WEBGL1: SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); break; - default:; + default: + _IRR_DEBUG_BREAK_IF(1); } if (CreationParams.DriverDebug) { diff --git a/irr/src/CIrrDeviceWin32.cpp b/irr/src/CIrrDeviceWin32.cpp index c2876fcce..fe5293988 100644 --- a/irr/src/CIrrDeviceWin32.cpp +++ b/irr/src/CIrrDeviceWin32.cpp @@ -29,7 +29,7 @@ #endif #endif -#if defined(_IRR_COMPILE_WITH_OGLES1_) || defined(_IRR_COMPILE_WITH_OGLES2_) +#if defined(_IRR_COMPILE_WITH_OGLES2_) #include "CEGLManager.h" #endif @@ -45,10 +45,6 @@ namespace video IVideoDriver *createOpenGLDriver(const irr::SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager); #endif -#ifdef _IRR_COMPILE_WITH_OGLES1_ -IVideoDriver *createOGLES1Driver(const irr::SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager); -#endif - #ifdef _IRR_COMPILE_WITH_OGLES2_ IVideoDriver *createOGLES2Driver(const irr::SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager); #endif @@ -890,21 +886,6 @@ void CIrrDeviceWin32::createDriver() os::Printer::log("Could not create OpenGL driver.", ELL_ERROR); #else os::Printer::log("OpenGL driver was not compiled in.", ELL_ERROR); -#endif - break; - case video::EDT_OGLES1: -#ifdef _IRR_COMPILE_WITH_OGLES1_ - switchToFullScreen(); - - ContextManager = new video::CEGLManager(); - ContextManager->initialize(CreationParams, video::SExposedVideoData(HWnd)); - - VideoDriver = video::createOGLES1Driver(CreationParams, FileSystem, ContextManager); - - if (!VideoDriver) - os::Printer::log("Could not create OpenGL-ES1 driver.", ELL_ERROR); -#else - os::Printer::log("OpenGL-ES1 driver was not compiled in.", ELL_ERROR); #endif break; case video::EDT_OGLES2: diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index d5e9d47e7..2bc935b9f 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -130,12 +130,6 @@ else() option(ENABLE_OPENGL "Enable OpenGL" TRUE) endif() -if(USE_SDL2 OR EMSCRIPTEN OR APPLE) - set(ENABLE_GLES1 FALSE) -else() - option(ENABLE_GLES1 "Enable OpenGL ES" FALSE) -endif() - if(APPLE) set(ENABLE_GLES2 FALSE) set(ENABLE_WEBGL1 FALSE) @@ -172,14 +166,6 @@ if(ENABLE_OPENGL3) endif() endif() -if(ENABLE_GLES1) - add_definitions(-D_IRR_COMPILE_WITH_OGLES1_) - set(OPENGLES_DIRECT_LINK TRUE) - if(DEVICE MATCHES "^(WINDOWS|X11)$") - add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_) - endif() -endif() - if(ENABLE_GLES2) add_definitions(-D_IRR_COMPILE_WITH_OGLES2_) if(DEVICE MATCHES "^(WINDOWS|X11)$" OR EMSCRIPTEN) @@ -204,7 +190,6 @@ endif() message(STATUS "Device: ${DEVICE}") message(STATUS "OpenGL: ${ENABLE_OPENGL}") message(STATUS "OpenGL 3: ${ENABLE_OPENGL3}") -message(STATUS "OpenGL ES: ${ENABLE_GLES1}") if (ENABLE_GLES2) message(STATUS "OpenGL ES 2: ON (unified)") else() @@ -220,13 +205,6 @@ find_package(ZLIB REQUIRED) find_package(JPEG REQUIRED) find_package(PNG REQUIRED) -if(ENABLE_GLES1) - # only tested on Android, probably works on Linux (is this needed anywhere else?) - find_library(OPENGLES_LIBRARY NAMES GLESv1_CM REQUIRED) - find_library(EGL_LIBRARY NAMES EGL REQUIRED) - - message(STATUS "Found OpenGLES: ${OPENGLES_LIBRARY}") -endif() if(ENABLE_GLES2) find_package(OpenGLES2 REQUIRED) endif() @@ -360,14 +338,6 @@ if(ENABLE_OPENGL) ) endif() -if(ENABLE_GLES1) - set(IRRDRVROBJ - ${IRRDRVROBJ} - COGLESDriver.cpp - COGLESExtensionHandler.cpp - ) -endif() - # the unified drivers if(ENABLE_OPENGL3 OR ENABLE_GLES2) @@ -509,7 +479,6 @@ target_link_libraries(IrrlichtMt PRIVATE "$<$:SDL2::SDL2>" "$<$:${OPENGL_LIBRARIES}>" - "$<$:${OPENGLES_LIBRARY}>" ${EGL_LIBRARY} # incl. transitive SDL2 dependencies for static linking diff --git a/irr/src/COGLESCommon.h b/irr/src/COGLESCommon.h deleted file mode 100644 index 1d439546a..000000000 --- a/irr/src/COGLESCommon.h +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) 2015 Patryk Nadrowski -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#pragma once - -#ifdef _IRR_COMPILE_WITH_OGLES1_ - -#if defined(_IRR_COMPILE_WITH_IOS_DEVICE_) -#include -#include -#elif defined(_IRR_OGLES1_USE_KHRONOS_API_HEADERS_) -#include -#include -typedef char GLchar; -#else // or only when defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) ? -#include -#include -#include -#endif - -#ifndef GL_BGRA -#define GL_BGRA 0x80E1; -#endif - -// Blending definitions. - -#if defined(GL_OES_blend_subtract) -#define GL_FUNC_ADD GL_FUNC_ADD_OES -#else -#define GL_FUNC_ADD 0 -#endif - -// FBO definitions. - -#ifdef GL_OES_framebuffer_object -#define GL_NONE 0 // iOS has missing definition of GL_NONE_OES -#define GL_FRAMEBUFFER GL_FRAMEBUFFER_OES -#define GL_DEPTH_COMPONENT16 GL_DEPTH_COMPONENT16_OES -#define GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0_OES -#define GL_DEPTH_ATTACHMENT GL_DEPTH_ATTACHMENT_OES -#define GL_STENCIL_ATTACHMENT GL_STENCIL_ATTACHMENT_OES -#define GL_FRAMEBUFFER_COMPLETE GL_FRAMEBUFFER_COMPLETE_OES -#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 1 -#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 2 -#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES -#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES -#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES -#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES -#define GL_FRAMEBUFFER_UNSUPPORTED GL_FRAMEBUFFER_UNSUPPORTED_OES -#else -#define GL_NONE 0 -#define GL_FRAMEBUFFER 0 -#define GL_DEPTH_COMPONENT16 0 -#define GL_COLOR_ATTACHMENT0 0 -#define GL_DEPTH_ATTACHMENT 0 -#define GL_STENCIL_ATTACHMENT 0 -#define GL_FRAMEBUFFER_COMPLETE 0 -#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 1 -#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 2 -#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 3 -#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS 4 -#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 5 -#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 6 -#define GL_FRAMEBUFFER_UNSUPPORTED 7 -#endif - -#define GL_DEPTH_COMPONENT 0x1902 - -// Texture definitions. - -#ifdef GL_OES_texture_cube_map -#define GL_TEXTURE_CUBE_MAP GL_TEXTURE_CUBE_MAP_OES -#define GL_TEXTURE_CUBE_MAP_POSITIVE_X GL_TEXTURE_CUBE_MAP_POSITIVE_X_OES -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X GL_TEXTURE_CUBE_MAP_NEGATIVE_X_OES -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y GL_TEXTURE_CUBE_MAP_POSITIVE_Y_OES -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_OES -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z GL_TEXTURE_CUBE_MAP_POSITIVE_Z_OES -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_OES -#else -#define GL_TEXTURE_CUBE_MAP 0 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0 -#endif - -// to check if this header is in the current compile unit (different GL implementation used different "GLCommon" headers in Irrlicht -#define IRR_COMPILE_GLES_COMMON - -// macro used with COGLES1Driver -#define TEST_GL_ERROR(cls) (cls)->testGLError(__LINE__) - -namespace irr -{ -namespace video -{ - -// Forward declarations. - -class COpenGLCoreFeature; - -template -class COpenGLCoreTexture; - -template -class COpenGLCoreRenderTarget; - -template -class COpenGLCoreCacheHandler; - -class COGLES1Driver; -typedef COpenGLCoreTexture COGLES1Texture; -typedef COpenGLCoreRenderTarget COGLES1RenderTarget; -typedef COpenGLCoreCacheHandler COGLES1CacheHandler; - -} -} - -#endif diff --git a/irr/src/COGLESDriver.cpp b/irr/src/COGLESDriver.cpp deleted file mode 100644 index d5feda58e..000000000 --- a/irr/src/COGLESDriver.cpp +++ /dev/null @@ -1,2346 +0,0 @@ -// Copyright (C) 2002-2008 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#include "COGLESDriver.h" -#include -#include "CNullDriver.h" -#include "IContextManager.h" - -#ifdef _IRR_COMPILE_WITH_OGLES1_ - -#include "COpenGLCoreTexture.h" -#include "COpenGLCoreRenderTarget.h" -#include "COpenGLCoreCacheHandler.h" - -#include "COGLESMaterialRenderer.h" - -#include "EVertexAttributes.h" -#include "CImage.h" -#include "os.h" - -namespace irr -{ -namespace video -{ - -COGLES1Driver::COGLES1Driver(const SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager) : - CNullDriver(io, params.WindowSize), COGLES1ExtensionHandler(), CacheHandler(0), CurrentRenderMode(ERM_NONE), - ResetRenderStates(true), Transformation3DChanged(true), AntiAlias(params.AntiAlias), - ColorFormat(ECF_R8G8B8), Params(params), ContextManager(contextManager) -{ -#ifdef _DEBUG - setDebugName("COGLESDriver"); -#endif - - core::dimension2d windowSize(0, 0); - - if (!ContextManager) - return; - - ContextManager->grab(); - ContextManager->generateSurface(); - ContextManager->generateContext(); - ExposedData = ContextManager->getContext(); - ContextManager->activateContext(ExposedData, false); - - windowSize = params.WindowSize; - - genericDriverInit(windowSize, params.Stencilbuffer); -} - -COGLES1Driver::~COGLES1Driver() -{ - deleteMaterialRenders(); - - CacheHandler->getTextureCache().clear(); - - removeAllRenderTargets(); - deleteAllTextures(); - removeAllOcclusionQueries(); - removeAllHardwareBuffers(); - - delete CacheHandler; - - if (ContextManager) { - ContextManager->destroyContext(); - ContextManager->destroySurface(); - ContextManager->terminate(); - ContextManager->drop(); - } -} - -// ----------------------------------------------------------------------- -// METHODS -// ----------------------------------------------------------------------- - -bool COGLES1Driver::genericDriverInit(const core::dimension2d &screenSize, bool stencilBuffer) -{ - Name = glGetString(GL_VERSION); - printVersion(); - - // print renderer information - VendorName = glGetString(GL_VENDOR); - os::Printer::log(VendorName.c_str(), ELL_INFORMATION); - - // load extensions - initExtensions(); - - // reset cache handler - delete CacheHandler; - CacheHandler = new COGLES1CacheHandler(this); - - StencilBuffer = stencilBuffer; - - DriverAttributes->setAttribute("MaxTextures", (s32)Feature.MaxTextureUnits); - DriverAttributes->setAttribute("MaxSupportedTextures", (s32)Feature.MaxTextureUnits); - DriverAttributes->setAttribute("MaxAnisotropy", MaxAnisotropy); - DriverAttributes->setAttribute("MaxIndices", (s32)MaxIndices); - DriverAttributes->setAttribute("MaxTextureSize", (s32)MaxTextureSize); - DriverAttributes->setAttribute("MaxTextureLODBias", MaxTextureLODBias); - DriverAttributes->setAttribute("Version", Version); - DriverAttributes->setAttribute("AntiAlias", AntiAlias); - - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - for (s32 i = 0; i < ETS_COUNT; ++i) - setTransform(static_cast(i), core::IdentityMatrix); - - setAmbientLight(SColorf(0.0f, 0.0f, 0.0f, 0.0f)); - glClearDepthf(1.0f); - - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); - glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST); - glDepthFunc(GL_LEQUAL); - glFrontFace(GL_CW); - glAlphaFunc(GL_GREATER, 0.f); - - // create material renderers - createMaterialRenderers(); - - // set the renderstates - setRenderStates3DMode(); - - // set fog mode - setFog(FogColor, FogType, FogStart, FogEnd, FogDensity, PixelFog, RangeFog); - - // create matrix for flipping textures - TextureFlipMatrix.buildTextureTransform(0.0f, core::vector2df(0, 0), core::vector2df(0, 1.0f), core::vector2df(1.0f, -1.0f)); - - // We need to reset once more at the beginning of the first rendering. - // This fixes problems with intermediate changes to the material during texture load. - ResetRenderStates = true; - - testGLError(__LINE__); - - return true; -} - -void COGLES1Driver::createMaterialRenderers() -{ - addAndDropMaterialRenderer(new COGLES1MaterialRenderer_SOLID(this)); - addAndDropMaterialRenderer(new COGLES1MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL(this)); - addAndDropMaterialRenderer(new COGLES1MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL_REF(this)); - addAndDropMaterialRenderer(new COGLES1MaterialRenderer_TRANSPARENT_VERTEX_ALPHA(this)); - addAndDropMaterialRenderer(new COGLES1MaterialRenderer_ONETEXTURE_BLEND(this)); -} - -bool COGLES1Driver::beginScene(u16 clearFlag, SColor clearColor, f32 clearDepth, u8 clearStencil, const SExposedVideoData &videoData, core::rect *sourceRect) -{ - CNullDriver::beginScene(clearFlag, clearColor, clearDepth, clearStencil, videoData, sourceRect); - - if (ContextManager) - ContextManager->activateContext(videoData, true); - - clearBuffers(clearFlag, clearColor, clearDepth, clearStencil); - - return true; -} - -bool COGLES1Driver::endScene() -{ - CNullDriver::endScene(); - - glFlush(); - - if (ContextManager) - return ContextManager->swapBuffers(); - - return false; -} - -//! Returns the transformation set by setTransform -const core::matrix4 &COGLES1Driver::getTransform(E_TRANSFORMATION_STATE state) const -{ - return Matrices[state]; -} - -//! sets transformation -void COGLES1Driver::setTransform(E_TRANSFORMATION_STATE state, const core::matrix4 &mat) -{ - Matrices[state] = mat; - Transformation3DChanged = true; - - switch (state) { - case ETS_VIEW: - case ETS_WORLD: { - // OGLES1 only has a model matrix, view and world is not existent. so lets fake these two. - glMatrixMode(GL_MODELVIEW); - glLoadMatrixf((Matrices[ETS_VIEW] * Matrices[ETS_WORLD]).pointer()); - } break; - case ETS_PROJECTION: { - GLfloat glmat[16]; - getGLMatrix(glmat, mat); - // flip z to compensate OGLES1s right-hand coordinate system - glmat[12] *= -1.0f; - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(glmat); - } break; - default: - break; - } -} - -bool COGLES1Driver::updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) -{ - if (!HWBuffer) - return false; - - const scene::IMeshBuffer *mb = HWBuffer->MeshBuffer; - const void *vertices = mb->getVertices(); - const u32 vertexCount = mb->getVertexCount(); - const E_VERTEX_TYPE vType = mb->getVertexType(); - const u32 vertexSize = getVertexPitchFromType(vType); - - // buffer vertex data, and convert colours... - core::array buffer(vertexSize * vertexCount); - buffer.set_used(vertexSize * vertexCount); - memcpy(buffer.pointer(), vertices, vertexSize * vertexCount); - - // in order to convert the colors into opengl format (RGBA) - switch (vType) { - case EVT_STANDARD: { - S3DVertex *pb = reinterpret_cast(buffer.pointer()); - const S3DVertex *po = static_cast(vertices); - for (u32 i = 0; i < vertexCount; i++) { - po[i].Color.toOpenGLColor((u8 *)&(pb[i].Color.color)); - } - } break; - case EVT_2TCOORDS: { - S3DVertex2TCoords *pb = reinterpret_cast(buffer.pointer()); - const S3DVertex2TCoords *po = static_cast(vertices); - for (u32 i = 0; i < vertexCount; i++) { - po[i].Color.toOpenGLColor((u8 *)&(pb[i].Color.color)); - } - } break; - case EVT_TANGENTS: { - S3DVertexTangents *pb = reinterpret_cast(buffer.pointer()); - const S3DVertexTangents *po = static_cast(vertices); - for (u32 i = 0; i < vertexCount; i++) { - po[i].Color.toOpenGLColor((u8 *)&(pb[i].Color.color)); - } - } break; - default: { - return false; - } - } - - // get or create buffer - bool newBuffer = false; - if (!HWBuffer->vbo_verticesID) { - glGenBuffers(1, &HWBuffer->vbo_verticesID); - if (!HWBuffer->vbo_verticesID) - return false; - newBuffer = true; - } else if (HWBuffer->vbo_verticesSize < vertexCount * vertexSize) { - newBuffer = true; - } - - glBindBuffer(GL_ARRAY_BUFFER, HWBuffer->vbo_verticesID); - - // copy data to graphics card - if (!newBuffer) - glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * vertexSize, buffer.const_pointer()); - else { - HWBuffer->vbo_verticesSize = vertexCount * vertexSize; - - if (HWBuffer->Mapped_Vertex == scene::EHM_STATIC) - glBufferData(GL_ARRAY_BUFFER, vertexCount * vertexSize, buffer.const_pointer(), GL_STATIC_DRAW); - else - glBufferData(GL_ARRAY_BUFFER, vertexCount * vertexSize, buffer.const_pointer(), GL_DYNAMIC_DRAW); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - - return (!testGLError(__LINE__)); -} - -bool COGLES1Driver::updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) -{ - if (!HWBuffer) - return false; - - const scene::IMeshBuffer *mb = HWBuffer->MeshBuffer; - - const void *indices = mb->getIndices(); - u32 indexCount = mb->getIndexCount(); - - GLenum indexSize; - switch (mb->getIndexType()) { - case (EIT_16BIT): { - indexSize = sizeof(u16); - break; - } - case (EIT_32BIT): { - indexSize = sizeof(u32); - break; - } - default: { - return false; - } - } - - // get or create buffer - bool newBuffer = false; - if (!HWBuffer->vbo_indicesID) { - glGenBuffers(1, &HWBuffer->vbo_indicesID); - if (!HWBuffer->vbo_indicesID) - return false; - newBuffer = true; - } else if (HWBuffer->vbo_indicesSize < indexCount * indexSize) { - newBuffer = true; - } - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, HWBuffer->vbo_indicesID); - - // copy data to graphics card - if (!newBuffer) - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexCount * indexSize, indices); - else { - HWBuffer->vbo_indicesSize = indexCount * indexSize; - - if (HWBuffer->Mapped_Index == scene::EHM_STATIC) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * indexSize, indices, GL_STATIC_DRAW); - else - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * indexSize, indices, GL_DYNAMIC_DRAW); - } - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - return (!testGLError(__LINE__)); -} - -//! updates hardware buffer if needed -bool COGLES1Driver::updateHardwareBuffer(SHWBufferLink *HWBuffer) -{ - if (!HWBuffer) - return false; - - if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) { - if (HWBuffer->ChangedID_Vertex != HWBuffer->MeshBuffer->getChangedID_Vertex() || !static_cast(HWBuffer)->vbo_verticesID) { - - HWBuffer->ChangedID_Vertex = HWBuffer->MeshBuffer->getChangedID_Vertex(); - - if (!updateVertexHardwareBuffer(static_cast(HWBuffer))) - return false; - } - } - - if (HWBuffer->Mapped_Index != scene::EHM_NEVER) { - if (HWBuffer->ChangedID_Index != HWBuffer->MeshBuffer->getChangedID_Index() || !((SHWBufferLink_opengl *)HWBuffer)->vbo_indicesID) { - - HWBuffer->ChangedID_Index = HWBuffer->MeshBuffer->getChangedID_Index(); - - if (!updateIndexHardwareBuffer(static_cast(HWBuffer))) - return false; - } - } - - return true; -} - -//! Create hardware buffer from meshbuffer -COGLES1Driver::SHWBufferLink *COGLES1Driver::createHardwareBuffer(const scene::IMeshBuffer *mb) -{ - if (!mb || (mb->getHardwareMappingHint_Index() == scene::EHM_NEVER && mb->getHardwareMappingHint_Vertex() == scene::EHM_NEVER)) - return 0; - - SHWBufferLink_opengl *HWBuffer = new SHWBufferLink_opengl(mb); - - // add to map - HWBuffer->listPosition = HWBufferList.insert(HWBufferList.end(), HWBuffer); - - HWBuffer->ChangedID_Vertex = HWBuffer->MeshBuffer->getChangedID_Vertex(); - HWBuffer->ChangedID_Index = HWBuffer->MeshBuffer->getChangedID_Index(); - HWBuffer->Mapped_Vertex = mb->getHardwareMappingHint_Vertex(); - HWBuffer->Mapped_Index = mb->getHardwareMappingHint_Index(); - HWBuffer->vbo_verticesID = 0; - HWBuffer->vbo_indicesID = 0; - HWBuffer->vbo_verticesSize = 0; - HWBuffer->vbo_indicesSize = 0; - - if (!updateHardwareBuffer(HWBuffer)) { - deleteHardwareBuffer(HWBuffer); - return 0; - } - - return HWBuffer; -} - -void COGLES1Driver::deleteHardwareBuffer(SHWBufferLink *_HWBuffer) -{ - if (!_HWBuffer) - return; - - SHWBufferLink_opengl *HWBuffer = static_cast(_HWBuffer); - if (HWBuffer->vbo_verticesID) { - glDeleteBuffers(1, &HWBuffer->vbo_verticesID); - HWBuffer->vbo_verticesID = 0; - } - if (HWBuffer->vbo_indicesID) { - glDeleteBuffers(1, &HWBuffer->vbo_indicesID); - HWBuffer->vbo_indicesID = 0; - } - - CNullDriver::deleteHardwareBuffer(_HWBuffer); -} - -//! Draw hardware buffer -void COGLES1Driver::drawHardwareBuffer(SHWBufferLink *_HWBuffer) -{ - if (!_HWBuffer) - return; - - SHWBufferLink_opengl *HWBuffer = static_cast(_HWBuffer); - - updateHardwareBuffer(HWBuffer); // check if update is needed - - const scene::IMeshBuffer *mb = HWBuffer->MeshBuffer; - const void *vertices = mb->getVertices(); - const void *indexList = mb->getIndices(); - - if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) { - glBindBuffer(GL_ARRAY_BUFFER, HWBuffer->vbo_verticesID); - vertices = 0; - } - - if (HWBuffer->Mapped_Index != scene::EHM_NEVER) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, HWBuffer->vbo_indicesID); - indexList = 0; - } - - drawVertexPrimitiveList(vertices, mb->getVertexCount(), indexList, - mb->getPrimitiveCount(), mb->getVertexType(), - mb->getPrimitiveType(), mb->getIndexType()); - - if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) - glBindBuffer(GL_ARRAY_BUFFER, 0); - - if (HWBuffer->Mapped_Index != scene::EHM_NEVER) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); -} - -IRenderTarget *COGLES1Driver::addRenderTarget() -{ - COGLES1RenderTarget *renderTarget = new COGLES1RenderTarget(this); - RenderTargets.push_back(renderTarget); - - return renderTarget; -} - -// small helper function to create vertex buffer object adress offsets -static inline u8 *buffer_offset(const long offset) -{ - return ((u8 *)0 + offset); -} - -//! draws a vertex primitive list -void COGLES1Driver::drawVertexPrimitiveList(const void *vertices, u32 vertexCount, - const void *indexList, u32 primitiveCount, - E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType) -{ - if (!checkPrimitiveCount(primitiveCount)) - return; - - setRenderStates3DMode(); - - drawVertexPrimitiveList2d3d(vertices, vertexCount, (const u16 *)indexList, primitiveCount, vType, pType, iType); -} - -void COGLES1Driver::drawVertexPrimitiveList2d3d(const void *vertices, u32 vertexCount, - const void *indexList, u32 primitiveCount, - E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType, bool threed) -{ - if (!primitiveCount || !vertexCount) - return; - - if (!threed && !checkPrimitiveCount(primitiveCount)) - return; - - CNullDriver::drawVertexPrimitiveList(vertices, vertexCount, indexList, primitiveCount, vType, pType, iType); - - if (vertices) { - // convert colors to gl color format. - vertexCount *= 4; // reused as color component count - ColorBuffer.set_used(vertexCount); - u32 i; - - switch (vType) { - case EVT_STANDARD: { - const S3DVertex *p = static_cast(vertices); - for (i = 0; i < vertexCount; i += 4) { - p->Color.toOpenGLColor(&ColorBuffer[i]); - ++p; - } - } break; - case EVT_2TCOORDS: { - const S3DVertex2TCoords *p = static_cast(vertices); - for (i = 0; i < vertexCount; i += 4) { - p->Color.toOpenGLColor(&ColorBuffer[i]); - ++p; - } - } break; - case EVT_TANGENTS: { - const S3DVertexTangents *p = static_cast(vertices); - for (i = 0; i < vertexCount; i += 4) { - p->Color.toOpenGLColor(&ColorBuffer[i]); - ++p; - } - } break; - } - } - - // draw everything - glClientActiveTexture(GL_TEXTURE0); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_VERTEX_ARRAY); - if ((pType != scene::EPT_POINTS) && (pType != scene::EPT_POINT_SPRITES)) - glEnableClientState(GL_TEXTURE_COORD_ARRAY); -#ifdef GL_OES_point_size_array - else if (FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_OES_point_size_array] && (Material.Thickness == 0.0f)) - glEnableClientState(GL_POINT_SIZE_ARRAY_OES); -#endif - if (threed && (pType != scene::EPT_POINTS) && (pType != scene::EPT_POINT_SPRITES)) - glEnableClientState(GL_NORMAL_ARRAY); - - if (vertices) - glColorPointer(4, GL_UNSIGNED_BYTE, 0, &ColorBuffer[0]); - - switch (vType) { - case EVT_STANDARD: - if (vertices) { - if (threed) - glNormalPointer(GL_FLOAT, sizeof(S3DVertex), &(static_cast(vertices))[0].Normal); - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertex), &(static_cast(vertices))[0].TCoords); - glVertexPointer((threed ? 3 : 2), GL_FLOAT, sizeof(S3DVertex), &(static_cast(vertices))[0].Pos); - } else { - glNormalPointer(GL_FLOAT, sizeof(S3DVertex), buffer_offset(12)); - glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(S3DVertex), buffer_offset(24)); - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertex), buffer_offset(28)); - glVertexPointer(3, GL_FLOAT, sizeof(S3DVertex), 0); - } - - if (Feature.MaxTextureUnits > 0 && CacheHandler->getTextureCache().get(1)) { - glClientActiveTexture(GL_TEXTURE0 + 1); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - if (vertices) - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertex), &(static_cast(vertices))[0].TCoords); - else - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertex), buffer_offset(28)); - } - break; - case EVT_2TCOORDS: - if (vertices) { - if (threed) - glNormalPointer(GL_FLOAT, sizeof(S3DVertex2TCoords), &(static_cast(vertices))[0].Normal); - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertex2TCoords), &(static_cast(vertices))[0].TCoords); - glVertexPointer((threed ? 3 : 2), GL_FLOAT, sizeof(S3DVertex2TCoords), &(static_cast(vertices))[0].Pos); - } else { - glNormalPointer(GL_FLOAT, sizeof(S3DVertex2TCoords), buffer_offset(12)); - glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(S3DVertex2TCoords), buffer_offset(24)); - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertex2TCoords), buffer_offset(28)); - glVertexPointer(3, GL_FLOAT, sizeof(S3DVertex2TCoords), buffer_offset(0)); - } - - if (Feature.MaxTextureUnits > 0) { - glClientActiveTexture(GL_TEXTURE0 + 1); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - if (vertices) - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertex2TCoords), &(static_cast(vertices))[0].TCoords2); - else - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertex2TCoords), buffer_offset(36)); - } - break; - case EVT_TANGENTS: - if (vertices) { - if (threed) - glNormalPointer(GL_FLOAT, sizeof(S3DVertexTangents), &(static_cast(vertices))[0].Normal); - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertexTangents), &(static_cast(vertices))[0].TCoords); - glVertexPointer((threed ? 3 : 2), GL_FLOAT, sizeof(S3DVertexTangents), &(static_cast(vertices))[0].Pos); - } else { - glNormalPointer(GL_FLOAT, sizeof(S3DVertexTangents), buffer_offset(12)); - glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(S3DVertexTangents), buffer_offset(24)); - glTexCoordPointer(2, GL_FLOAT, sizeof(S3DVertexTangents), buffer_offset(28)); - glVertexPointer(3, GL_FLOAT, sizeof(S3DVertexTangents), buffer_offset(0)); - } - - if (Feature.MaxTextureUnits > 0) { - glClientActiveTexture(GL_TEXTURE0 + 1); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - if (vertices) - glTexCoordPointer(3, GL_FLOAT, sizeof(S3DVertexTangents), &(static_cast(vertices))[0].Tangent); - else - glTexCoordPointer(3, GL_FLOAT, sizeof(S3DVertexTangents), buffer_offset(36)); - - glClientActiveTexture(GL_TEXTURE0 + 2); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - if (vertices) - glTexCoordPointer(3, GL_FLOAT, sizeof(S3DVertexTangents), &(static_cast(vertices))[0].Binormal); - else - glTexCoordPointer(3, GL_FLOAT, sizeof(S3DVertexTangents), buffer_offset(48)); - } - break; - } - - GLenum indexSize = 0; - - switch (iType) { - case (EIT_16BIT): { - indexSize = GL_UNSIGNED_SHORT; - break; - } - case (EIT_32BIT): { -#ifdef GL_OES_element_index_uint -#ifndef GL_UNSIGNED_INT -#define GL_UNSIGNED_INT 0x1405 -#endif - if (FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_OES_element_index_uint]) - indexSize = GL_UNSIGNED_INT; - else -#endif - indexSize = GL_UNSIGNED_SHORT; - break; - } - } - - switch (pType) { - case scene::EPT_POINTS: - case scene::EPT_POINT_SPRITES: { -#ifdef GL_OES_point_sprite - if (pType == scene::EPT_POINT_SPRITES && FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_OES_point_sprite]) - glEnable(GL_POINT_SPRITE_OES); -#endif - // if ==0 we use the point size array - if (Material.Thickness != 0.f) { - float quadratic[] = {0.0f, 0.0f, 10.01f}; - glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, quadratic); - float maxParticleSize = 1.0f; - glGetFloatv(GL_POINT_SIZE_MAX, &maxParticleSize); - // maxParticleSize=maxParticleSize 0) { - if (vType == EVT_TANGENTS) { - glClientActiveTexture(GL_TEXTURE0 + 2); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - } - if ((vType != EVT_STANDARD) || CacheHandler->getTextureCache().get(1)) { - glClientActiveTexture(GL_TEXTURE0 + 1); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - } - glClientActiveTexture(GL_TEXTURE0); - } - -#ifdef GL_OES_point_size_array - if (FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_OES_point_size_array] && (Material.Thickness == 0.0f)) - glDisableClientState(GL_POINT_SIZE_ARRAY_OES); -#endif - - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_NORMAL_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); -} - -//! draws a 2d image, using a color and the alpha channel of the texture -void COGLES1Driver::draw2DImage(const video::ITexture *texture, - const core::position2d &pos, - const core::rect &sourceRect, - const core::rect *clipRect, SColor color, - bool useAlphaChannelOfTexture) -{ - if (!texture) - return; - - if (!sourceRect.isValid()) - return; - - core::position2d targetPos(pos); - core::position2d sourcePos(sourceRect.UpperLeftCorner); - core::dimension2d sourceSize(sourceRect.getSize()); - if (clipRect) { - if (targetPos.X < clipRect->UpperLeftCorner.X) { - sourceSize.Width += targetPos.X - clipRect->UpperLeftCorner.X; - if (sourceSize.Width <= 0) - return; - - sourcePos.X -= targetPos.X - clipRect->UpperLeftCorner.X; - targetPos.X = clipRect->UpperLeftCorner.X; - } - - if (targetPos.X + sourceSize.Width > clipRect->LowerRightCorner.X) { - sourceSize.Width -= (targetPos.X + sourceSize.Width) - clipRect->LowerRightCorner.X; - if (sourceSize.Width <= 0) - return; - } - - if (targetPos.Y < clipRect->UpperLeftCorner.Y) { - sourceSize.Height += targetPos.Y - clipRect->UpperLeftCorner.Y; - if (sourceSize.Height <= 0) - return; - - sourcePos.Y -= targetPos.Y - clipRect->UpperLeftCorner.Y; - targetPos.Y = clipRect->UpperLeftCorner.Y; - } - - if (targetPos.Y + sourceSize.Height > clipRect->LowerRightCorner.Y) { - sourceSize.Height -= (targetPos.Y + sourceSize.Height) - clipRect->LowerRightCorner.Y; - if (sourceSize.Height <= 0) - return; - } - } - - // clip these coordinates - - if (targetPos.X < 0) { - sourceSize.Width += targetPos.X; - if (sourceSize.Width <= 0) - return; - - sourcePos.X -= targetPos.X; - targetPos.X = 0; - } - - const core::dimension2d &renderTargetSize = getCurrentRenderTargetSize(); - - if (targetPos.X + sourceSize.Width > (s32)renderTargetSize.Width) { - sourceSize.Width -= (targetPos.X + sourceSize.Width) - renderTargetSize.Width; - if (sourceSize.Width <= 0) - return; - } - - if (targetPos.Y < 0) { - sourceSize.Height += targetPos.Y; - if (sourceSize.Height <= 0) - return; - - sourcePos.Y -= targetPos.Y; - targetPos.Y = 0; - } - - if (targetPos.Y + sourceSize.Height > (s32)renderTargetSize.Height) { - sourceSize.Height -= (targetPos.Y + sourceSize.Height) - renderTargetSize.Height; - if (sourceSize.Height <= 0) - return; - } - - // ok, we've clipped everything. - // now draw it. - - // texcoords need to be flipped horizontally for RTTs - const bool isRTT = texture->isRenderTarget(); - const core::dimension2d &ss = texture->getOriginalSize(); - const f32 invW = 1.f / static_cast(ss.Width); - const f32 invH = 1.f / static_cast(ss.Height); - const core::rect tcoords( - sourcePos.X * invW, - (isRTT ? (sourcePos.Y + sourceSize.Height) : sourcePos.Y) * invH, - (sourcePos.X + sourceSize.Width) * invW, - (isRTT ? sourcePos.Y : (sourcePos.Y + sourceSize.Height)) * invH); - - const core::rect poss(targetPos, sourceSize); - - if (!CacheHandler->getTextureCache().set(0, texture)) - return; - - setRenderStates2DMode(color.getAlpha() < 255, true, useAlphaChannelOfTexture); - - u16 indices[] = {0, 1, 2, 3}; - S3DVertex vertices[4]; - vertices[0] = S3DVertex((f32)poss.UpperLeftCorner.X, (f32)poss.UpperLeftCorner.Y, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y); - vertices[1] = S3DVertex((f32)poss.LowerRightCorner.X, (f32)poss.UpperLeftCorner.Y, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y); - vertices[2] = S3DVertex((f32)poss.LowerRightCorner.X, (f32)poss.LowerRightCorner.Y, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y); - vertices[3] = S3DVertex((f32)poss.UpperLeftCorner.X, (f32)poss.LowerRightCorner.Y, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y); - drawVertexPrimitiveList2d3d(vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false); -} - -//! The same, but with a four element array of colors, one for each vertex -void COGLES1Driver::draw2DImage(const video::ITexture *texture, const core::rect &destRect, - const core::rect &sourceRect, const core::rect *clipRect, - const video::SColor *const colors, bool useAlphaChannelOfTexture) -{ - if (!texture) - return; - - // texcoords need to be flipped horizontally for RTTs - const bool isRTT = texture->isRenderTarget(); - const core::dimension2du &ss = texture->getOriginalSize(); - const f32 invW = 1.f / static_cast(ss.Width); - const f32 invH = 1.f / static_cast(ss.Height); - const core::rect tcoords( - sourceRect.UpperLeftCorner.X * invW, - (isRTT ? sourceRect.LowerRightCorner.Y : sourceRect.UpperLeftCorner.Y) * invH, - sourceRect.LowerRightCorner.X * invW, - (isRTT ? sourceRect.UpperLeftCorner.Y : sourceRect.LowerRightCorner.Y) * invH); - - const video::SColor temp[4] = { - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF, - 0xFFFFFFFF, - }; - - const video::SColor *const useColor = colors ? colors : temp; - - if (!CacheHandler->getTextureCache().set(0, texture)) - return; - - setRenderStates2DMode(useColor[0].getAlpha() < 255 || useColor[1].getAlpha() < 255 || - useColor[2].getAlpha() < 255 || useColor[3].getAlpha() < 255, - true, useAlphaChannelOfTexture); - - if (clipRect) { - if (!clipRect->isValid()) - return; - - glEnable(GL_SCISSOR_TEST); - const core::dimension2d &renderTargetSize = getCurrentRenderTargetSize(); - glScissor(clipRect->UpperLeftCorner.X, renderTargetSize.Height - clipRect->LowerRightCorner.Y, - clipRect->getWidth(), clipRect->getHeight()); - } - - u16 indices[] = {0, 1, 2, 3}; - S3DVertex vertices[4]; - vertices[0] = S3DVertex((f32)destRect.UpperLeftCorner.X, (f32)destRect.UpperLeftCorner.Y, 0, 0, 0, 1, useColor[0], tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y); - vertices[1] = S3DVertex((f32)destRect.LowerRightCorner.X, (f32)destRect.UpperLeftCorner.Y, 0, 0, 0, 1, useColor[3], tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y); - vertices[2] = S3DVertex((f32)destRect.LowerRightCorner.X, (f32)destRect.LowerRightCorner.Y, 0, 0, 0, 1, useColor[2], tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y); - vertices[3] = S3DVertex((f32)destRect.UpperLeftCorner.X, (f32)destRect.LowerRightCorner.Y, 0, 0, 0, 1, useColor[1], tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y); - drawVertexPrimitiveList2d3d(vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false); - - if (clipRect) - glDisable(GL_SCISSOR_TEST); -} - -void COGLES1Driver::draw2DImage(const video::ITexture *texture, u32 layer, bool flip) -{ - if (!texture || !CacheHandler->getTextureCache().set(0, texture)) - return; - - setRenderStates2DMode(false, true, true); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - Transformation3DChanged = true; - - u16 indices[] = {0, 1, 2, 3}; - S3DVertex vertices[4]; - - vertices[0].Pos = core::vector3df(-1.f, 1.f, 0.f); - vertices[1].Pos = core::vector3df(1.f, 1.f, 0.f); - vertices[2].Pos = core::vector3df(1.f, -1.f, 0.f); - vertices[3].Pos = core::vector3df(-1.f, -1.f, 0.f); - - f32 modificator = (flip) ? 1.f : 0.f; - - vertices[0].TCoords = core::vector2df(0.f, 0.f + modificator); - vertices[1].TCoords = core::vector2df(1.f, 0.f + modificator); - vertices[2].TCoords = core::vector2df(1.f, 1.f - modificator); - vertices[3].TCoords = core::vector2df(0.f, 1.f - modificator); - - vertices[0].Color = 0xFFFFFFFF; - vertices[1].Color = 0xFFFFFFFF; - vertices[2].Color = 0xFFFFFFFF; - vertices[3].Color = 0xFFFFFFFF; - - drawVertexPrimitiveList2d3d(vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false); -} - -//! draws a set of 2d images, using a color and the alpha channel of the texture if desired. -void COGLES1Driver::draw2DImageBatch(const video::ITexture *texture, - const core::array> &positions, - const core::array> &sourceRects, - const core::rect *clipRect, - SColor color, - bool useAlphaChannelOfTexture) -{ - if (!texture) - return; - - const u32 drawCount = core::min_(positions.size(), sourceRects.size()); - if (!drawCount) - return; - - const core::dimension2d &ss = texture->getOriginalSize(); - if (!ss.Width || !ss.Height) - return; - const f32 invW = 1.f / static_cast(ss.Width); - const f32 invH = 1.f / static_cast(ss.Height); - const core::dimension2d &renderTargetSize = getCurrentRenderTargetSize(); - - if (!CacheHandler->getTextureCache().set(0, texture)) - return; - - setRenderStates2DMode(color.getAlpha() < 255, true, useAlphaChannelOfTexture); - - core::array vertices; - core::array quadIndices; - vertices.reallocate(drawCount * 4); - quadIndices.reallocate(drawCount * 6); - - for (u32 i = 0; i < drawCount; ++i) { - if (!sourceRects[i].isValid()) - continue; - - core::position2d targetPos(positions[i]); - core::position2d sourcePos(sourceRects[i].UpperLeftCorner); - // This needs to be signed as it may go negative. - core::dimension2d sourceSize(sourceRects[i].getSize()); - if (clipRect) { - if (targetPos.X < clipRect->UpperLeftCorner.X) { - sourceSize.Width += targetPos.X - clipRect->UpperLeftCorner.X; - if (sourceSize.Width <= 0) - continue; - - sourcePos.X -= targetPos.X - clipRect->UpperLeftCorner.X; - targetPos.X = clipRect->UpperLeftCorner.X; - } - - if (targetPos.X + sourceSize.Width > clipRect->LowerRightCorner.X) { - sourceSize.Width -= (targetPos.X + sourceSize.Width) - clipRect->LowerRightCorner.X; - if (sourceSize.Width <= 0) - continue; - } - - if (targetPos.Y < clipRect->UpperLeftCorner.Y) { - sourceSize.Height += targetPos.Y - clipRect->UpperLeftCorner.Y; - if (sourceSize.Height <= 0) - continue; - - sourcePos.Y -= targetPos.Y - clipRect->UpperLeftCorner.Y; - targetPos.Y = clipRect->UpperLeftCorner.Y; - } - - if (targetPos.Y + sourceSize.Height > clipRect->LowerRightCorner.Y) { - sourceSize.Height -= (targetPos.Y + sourceSize.Height) - clipRect->LowerRightCorner.Y; - if (sourceSize.Height <= 0) - continue; - } - } - - // clip these coordinates - - if (targetPos.X < 0) { - sourceSize.Width += targetPos.X; - if (sourceSize.Width <= 0) - continue; - - sourcePos.X -= targetPos.X; - targetPos.X = 0; - } - - if (targetPos.X + sourceSize.Width > (s32)renderTargetSize.Width) { - sourceSize.Width -= (targetPos.X + sourceSize.Width) - renderTargetSize.Width; - if (sourceSize.Width <= 0) - continue; - } - - if (targetPos.Y < 0) { - sourceSize.Height += targetPos.Y; - if (sourceSize.Height <= 0) - continue; - - sourcePos.Y -= targetPos.Y; - targetPos.Y = 0; - } - - if (targetPos.Y + sourceSize.Height > (s32)renderTargetSize.Height) { - sourceSize.Height -= (targetPos.Y + sourceSize.Height) - renderTargetSize.Height; - if (sourceSize.Height <= 0) - continue; - } - - // ok, we've clipped everything. - - const core::rect tcoords( - sourcePos.X * invW, - sourcePos.Y * invH, - (sourcePos.X + sourceSize.Width) * invW, - (sourcePos.Y + sourceSize.Height) * invH); - - const core::rect poss(targetPos, sourceSize); - - const u32 vstart = vertices.size(); - - vertices.push_back(S3DVertex((f32)poss.UpperLeftCorner.X, (f32)poss.UpperLeftCorner.Y, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y)); - vertices.push_back(S3DVertex((f32)poss.LowerRightCorner.X, (f32)poss.UpperLeftCorner.Y, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y)); - vertices.push_back(S3DVertex((f32)poss.LowerRightCorner.X, (f32)poss.LowerRightCorner.Y, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y)); - vertices.push_back(S3DVertex((f32)poss.UpperLeftCorner.X, (f32)poss.LowerRightCorner.Y, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y)); - - quadIndices.push_back(vstart); - quadIndices.push_back(vstart + 1); - quadIndices.push_back(vstart + 2); - quadIndices.push_back(vstart); - quadIndices.push_back(vstart + 2); - quadIndices.push_back(vstart + 3); - } - if (vertices.size()) - drawVertexPrimitiveList2d3d(vertices.pointer(), vertices.size(), - quadIndices.pointer(), vertices.size() / 2, - video::EVT_STANDARD, scene::EPT_TRIANGLES, - EIT_16BIT, false); -} - -//! draw a 2d rectangle -void COGLES1Driver::draw2DRectangle(SColor color, const core::rect &position, - const core::rect *clip) -{ - setRenderStates2DMode(color.getAlpha() < 255, false, false); - - core::rect pos = position; - - if (clip) - pos.clipAgainst(*clip); - - if (!pos.isValid()) - return; - - u16 indices[] = {0, 1, 2, 3}; - S3DVertex vertices[4]; - vertices[0] = S3DVertex((f32)pos.UpperLeftCorner.X, (f32)pos.UpperLeftCorner.Y, 0, 0, 0, 1, color, 0, 0); - vertices[1] = S3DVertex((f32)pos.LowerRightCorner.X, (f32)pos.UpperLeftCorner.Y, 0, 0, 0, 1, color, 0, 0); - vertices[2] = S3DVertex((f32)pos.LowerRightCorner.X, (f32)pos.LowerRightCorner.Y, 0, 0, 0, 1, color, 0, 0); - vertices[3] = S3DVertex((f32)pos.UpperLeftCorner.X, (f32)pos.LowerRightCorner.Y, 0, 0, 0, 1, color, 0, 0); - drawVertexPrimitiveList2d3d(vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false); -} - -//! draw an 2d rectangle -void COGLES1Driver::draw2DRectangle(const core::rect &position, - SColor colorLeftUp, SColor colorRightUp, SColor colorLeftDown, SColor colorRightDown, - const core::rect *clip) -{ - core::rect pos = position; - - if (clip) - pos.clipAgainst(*clip); - - if (!pos.isValid()) - return; - - setRenderStates2DMode(colorLeftUp.getAlpha() < 255 || - colorRightUp.getAlpha() < 255 || - colorLeftDown.getAlpha() < 255 || - colorRightDown.getAlpha() < 255, - false, false); - - u16 indices[] = {0, 1, 2, 3}; - S3DVertex vertices[4]; - vertices[0] = S3DVertex((f32)pos.UpperLeftCorner.X, (f32)pos.UpperLeftCorner.Y, 0, 0, 0, 1, colorLeftUp, 0, 0); - vertices[1] = S3DVertex((f32)pos.LowerRightCorner.X, (f32)pos.UpperLeftCorner.Y, 0, 0, 0, 1, colorRightUp, 0, 0); - vertices[2] = S3DVertex((f32)pos.LowerRightCorner.X, (f32)pos.LowerRightCorner.Y, 0, 0, 0, 1, colorRightDown, 0, 0); - vertices[3] = S3DVertex((f32)pos.UpperLeftCorner.X, (f32)pos.LowerRightCorner.Y, 0, 0, 0, 1, colorLeftDown, 0, 0); - drawVertexPrimitiveList2d3d(vertices, 4, indices, 2, video::EVT_STANDARD, scene::EPT_TRIANGLE_FAN, EIT_16BIT, false); -} - -//! Draws a 2d line. -void COGLES1Driver::draw2DLine(const core::position2d &start, - const core::position2d &end, - SColor color) -{ - setRenderStates2DMode(color.getAlpha() < 255, false, false); - - u16 indices[] = {0, 1}; - S3DVertex vertices[2]; - vertices[0] = S3DVertex((f32)start.X, (f32)start.Y, 0, 0, 0, 1, color, 0, 0); - vertices[1] = S3DVertex((f32)end.X, (f32)end.Y, 0, 0, 0, 1, color, 1, 1); - drawVertexPrimitiveList2d3d(vertices, 2, indices, 1, video::EVT_STANDARD, scene::EPT_LINES, EIT_16BIT, false); -} - -//! creates a matrix in supplied GLfloat array to pass to OGLES1 -inline void COGLES1Driver::getGLMatrix(GLfloat gl_matrix[16], const core::matrix4 &m) -{ - memcpy(gl_matrix, m.pointer(), 16 * sizeof(f32)); -} - -//! creates a opengltexturematrix from a D3D style texture matrix -inline void COGLES1Driver::getGLTextureMatrix(GLfloat *o, const core::matrix4 &m) -{ - o[0] = m[0]; - o[1] = m[1]; - o[2] = 0.f; - o[3] = 0.f; - - o[4] = m[4]; - o[5] = m[5]; - o[6] = 0.f; - o[7] = 0.f; - - o[8] = 0.f; - o[9] = 0.f; - o[10] = 1.f; - o[11] = 0.f; - - o[12] = m[8]; - o[13] = m[9]; - o[14] = 0.f; - o[15] = 1.f; -} - -ITexture *COGLES1Driver::createDeviceDependentTexture(const io::path &name, IImage *image) -{ - std::vector tmp { image }; - - COGLES1Texture *texture = new COGLES1Texture(name, tmp, ETT_2D, this); - - return texture; -} - -ITexture *COGLES1Driver::createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) -{ - COGLES1Texture *texture = new COGLES1Texture(name, image, ETT_CUBEMAP, this); - - return texture; -} - -//! Sets a material. All 3d drawing functions draw geometry now using this material. -void COGLES1Driver::setMaterial(const SMaterial &material) -{ - Material = material; - OverrideMaterial.apply(Material); - - for (u32 i = 0; i < Feature.MaxTextureUnits; ++i) - setTransform((E_TRANSFORMATION_STATE)(ETS_TEXTURE_0 + i), material.getTextureMatrix(i)); -} - -//! prints error if an error happened. -bool COGLES1Driver::testGLError(int code) -{ - if (!Params.DriverDebug) - return false; - - GLenum g = glGetError(); - switch (g) { - case GL_NO_ERROR: - return false; - case GL_INVALID_ENUM: - os::Printer::log("GL_INVALID_ENUM", core::stringc(code).c_str(), ELL_ERROR); - break; - case GL_INVALID_VALUE: - os::Printer::log("GL_INVALID_VALUE", core::stringc(code).c_str(), ELL_ERROR); - break; - case GL_INVALID_OPERATION: - os::Printer::log("GL_INVALID_OPERATION", core::stringc(code).c_str(), ELL_ERROR); - break; - case GL_STACK_OVERFLOW: - os::Printer::log("GL_STACK_OVERFLOW", core::stringc(code).c_str(), ELL_ERROR); - break; - case GL_STACK_UNDERFLOW: - os::Printer::log("GL_STACK_UNDERFLOW", core::stringc(code).c_str(), ELL_ERROR); - break; - case GL_OUT_OF_MEMORY: - os::Printer::log("GL_OUT_OF_MEMORY", core::stringc(code).c_str(), ELL_ERROR); - break; - }; - return true; -} - -//! sets the needed renderstates -void COGLES1Driver::setRenderStates3DMode() -{ - if (CurrentRenderMode != ERM_3D) { - // Reset Texture Stages - CacheHandler->setBlend(false); - glDisable(GL_ALPHA_TEST); - CacheHandler->setBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // switch back the matrices - glMatrixMode(GL_MODELVIEW); - glLoadMatrixf((Matrices[ETS_VIEW] * Matrices[ETS_WORLD]).pointer()); - - GLfloat glmat[16]; - getGLMatrix(glmat, Matrices[ETS_PROJECTION]); - glmat[12] *= -1.0f; - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(glmat); - - ResetRenderStates = true; - } - - if (ResetRenderStates || LastMaterial != Material) { - // unset old material - - if (LastMaterial.MaterialType != Material.MaterialType && - static_cast(LastMaterial.MaterialType) < MaterialRenderers.size()) - MaterialRenderers[LastMaterial.MaterialType].Renderer->OnUnsetMaterial(); - - // set new material. - if (static_cast(Material.MaterialType) < MaterialRenderers.size()) - MaterialRenderers[Material.MaterialType].Renderer->OnSetMaterial( - Material, LastMaterial, ResetRenderStates, this); - - LastMaterial = Material; - CacheHandler->correctCacheMaterial(LastMaterial); - ResetRenderStates = false; - } - - if (static_cast(Material.MaterialType) < MaterialRenderers.size()) - MaterialRenderers[Material.MaterialType].Renderer->OnRender(this, video::EVT_STANDARD); - - CurrentRenderMode = ERM_3D; -} - -GLint COGLES1Driver::getTextureWrapMode(u8 clamp) const -{ - switch (clamp) { - case ETC_CLAMP: - // return GL_CLAMP; not supported in ogl-es - return GL_CLAMP_TO_EDGE; - break; - case ETC_CLAMP_TO_EDGE: - return GL_CLAMP_TO_EDGE; - break; - case ETC_CLAMP_TO_BORDER: - // return GL_CLAMP_TO_BORDER; not supported in ogl-es - return GL_CLAMP_TO_EDGE; - break; - case ETC_MIRROR: -#ifdef GL_OES_texture_mirrored_repeat - if (FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_OES_texture_mirrored_repeat]) - return GL_MIRRORED_REPEAT_OES; - else -#endif - return GL_REPEAT; - break; - // the next three are not yet supported at all - case ETC_MIRROR_CLAMP: - case ETC_MIRROR_CLAMP_TO_EDGE: - case ETC_MIRROR_CLAMP_TO_BORDER: -#ifdef GL_OES_texture_mirrored_repeat - if (FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_OES_texture_mirrored_repeat]) - return GL_MIRRORED_REPEAT_OES; - else -#endif - return GL_CLAMP_TO_EDGE; - break; - case ETC_REPEAT: - default: - return GL_REPEAT; - break; - } -} - -//! Can be called by an IMaterialRenderer to make its work easier. -void COGLES1Driver::setBasicRenderStates(const SMaterial &material, const SMaterial &lastmaterial, - bool resetAllRenderStates) -{ - if (resetAllRenderStates || - lastmaterial.ColorMaterial != material.ColorMaterial) { - // we only have diffuse_and_ambient in ogl-es - if (material.ColorMaterial == ECM_DIFFUSE_AND_AMBIENT) - glEnable(GL_COLOR_MATERIAL); - else - glDisable(GL_COLOR_MATERIAL); - } - - if (resetAllRenderStates || - lastmaterial.AmbientColor != material.AmbientColor || - lastmaterial.DiffuseColor != material.DiffuseColor || - lastmaterial.EmissiveColor != material.EmissiveColor || - lastmaterial.ColorMaterial != material.ColorMaterial) { - GLfloat color[4]; - - const f32 inv = 1.0f / 255.0f; - - if ((material.ColorMaterial != video::ECM_AMBIENT) && - (material.ColorMaterial != video::ECM_DIFFUSE_AND_AMBIENT)) { - color[0] = material.AmbientColor.getRed() * inv; - color[1] = material.AmbientColor.getGreen() * inv; - color[2] = material.AmbientColor.getBlue() * inv; - color[3] = material.AmbientColor.getAlpha() * inv; - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color); - } - - if ((material.ColorMaterial != video::ECM_DIFFUSE) && - (material.ColorMaterial != video::ECM_DIFFUSE_AND_AMBIENT)) { - color[0] = material.DiffuseColor.getRed() * inv; - color[1] = material.DiffuseColor.getGreen() * inv; - color[2] = material.DiffuseColor.getBlue() * inv; - color[3] = material.DiffuseColor.getAlpha() * inv; - glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color); - } - - if (material.ColorMaterial != video::ECM_EMISSIVE) { - color[0] = material.EmissiveColor.getRed() * inv; - color[1] = material.EmissiveColor.getGreen() * inv; - color[2] = material.EmissiveColor.getBlue() * inv; - color[3] = material.EmissiveColor.getAlpha() * inv; - glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, color); - } - } - - if (resetAllRenderStates || - lastmaterial.SpecularColor != material.SpecularColor || - lastmaterial.Shininess != material.Shininess) { - GLfloat color[] = {0.f, 0.f, 0.f, 1.f}; - const f32 inv = 1.0f / 255.0f; - - // disable Specular colors if no shininess is set - if ((material.Shininess != 0.0f) && - (material.ColorMaterial != video::ECM_SPECULAR)) { -#ifdef GL_EXT_separate_specular_color - if (FeatureAvailable[IRR_EXT_separate_specular_color]) - glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); -#endif - glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material.Shininess); - color[0] = material.SpecularColor.getRed() * inv; - color[1] = material.SpecularColor.getGreen() * inv; - color[2] = material.SpecularColor.getBlue() * inv; - color[3] = material.SpecularColor.getAlpha() * inv; - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, color); - } -#ifdef GL_EXT_separate_specular_color - else if (FeatureAvailable[IRR_EXT_separate_specular_color]) - glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SINGLE_COLOR); -#endif - } - - // TODO ogl-es - // fillmode - // if (resetAllRenderStates || (lastmaterial.Wireframe != material.Wireframe) || (lastmaterial.PointCloud != material.PointCloud)) - // glPolygonMode(GL_FRONT_AND_BACK, material.Wireframe ? GL_LINE : material.PointCloud? GL_POINT : GL_FILL); - - // shademode - if (resetAllRenderStates || (lastmaterial.GouraudShading != material.GouraudShading)) { - if (material.GouraudShading) - glShadeModel(GL_SMOOTH); - else - glShadeModel(GL_FLAT); - } - - // lighting - if (resetAllRenderStates || (lastmaterial.Lighting != material.Lighting)) { - if (material.Lighting) - glEnable(GL_LIGHTING); - else - glDisable(GL_LIGHTING); - } - - // zbuffer - if (resetAllRenderStates || lastmaterial.ZBuffer != material.ZBuffer) { - switch (material.ZBuffer) { - case ECFN_DISABLED: - glDisable(GL_DEPTH_TEST); - break; - case ECFN_LESSEQUAL: - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LEQUAL); - break; - case ECFN_EQUAL: - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_EQUAL); - break; - case ECFN_LESS: - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - break; - case ECFN_NOTEQUAL: - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_NOTEQUAL); - break; - case ECFN_GREATEREQUAL: - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_GEQUAL); - break; - case ECFN_GREATER: - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_GREATER); - break; - case ECFN_ALWAYS: - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_ALWAYS); - break; - case ECFN_NEVER: - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_NEVER); - break; - } - } - - // zwrite - if (getWriteZBuffer(material)) { - glDepthMask(GL_TRUE); - } else { - glDepthMask(GL_FALSE); - } - - // back face culling - if (resetAllRenderStates || (lastmaterial.FrontfaceCulling != material.FrontfaceCulling) || (lastmaterial.BackfaceCulling != material.BackfaceCulling)) { - if ((material.FrontfaceCulling) && (material.BackfaceCulling)) { - glCullFace(GL_FRONT_AND_BACK); - glEnable(GL_CULL_FACE); - } else if (material.BackfaceCulling) { - glCullFace(GL_BACK); - glEnable(GL_CULL_FACE); - } else if (material.FrontfaceCulling) { - glCullFace(GL_FRONT); - glEnable(GL_CULL_FACE); - } else - glDisable(GL_CULL_FACE); - } - - // fog - if (resetAllRenderStates || lastmaterial.FogEnable != material.FogEnable) { - if (material.FogEnable) - glEnable(GL_FOG); - else - glDisable(GL_FOG); - } - - // normalization - if (resetAllRenderStates || lastmaterial.NormalizeNormals != material.NormalizeNormals) { - if (material.NormalizeNormals) - glEnable(GL_NORMALIZE); - else - glDisable(GL_NORMALIZE); - } - - // Color Mask - if (resetAllRenderStates || lastmaterial.ColorMask != material.ColorMask) { - glColorMask( - (material.ColorMask & ECP_RED) ? GL_TRUE : GL_FALSE, - (material.ColorMask & ECP_GREEN) ? GL_TRUE : GL_FALSE, - (material.ColorMask & ECP_BLUE) ? GL_TRUE : GL_FALSE, - (material.ColorMask & ECP_ALPHA) ? GL_TRUE : GL_FALSE); - } - - // Blend Equation - if (material.BlendOperation == EBO_NONE) - CacheHandler->setBlend(false); - else { - CacheHandler->setBlend(true); - - if (queryFeature(EVDF_BLEND_OPERATIONS)) { - switch (material.BlendOperation) { - case EBO_ADD: -#if defined(GL_OES_blend_subtract) - CacheHandler->setBlendEquation(GL_FUNC_ADD_OES); -#endif - break; - case EBO_SUBTRACT: -#if defined(GL_OES_blend_subtract) - CacheHandler->setBlendEquation(GL_FUNC_SUBTRACT_OES); -#endif - break; - case EBO_REVSUBTRACT: -#if defined(GL_OES_blend_subtract) - CacheHandler->setBlendEquation(GL_FUNC_REVERSE_SUBTRACT_OES); -#endif - break; - default: - break; - } - } - } - - // Blend Factor - if (IR(material.BlendFactor) & 0xFFFFFFFF // TODO: why the & 0xFFFFFFFF? - && material.MaterialType != EMT_ONETEXTURE_BLEND) { - E_BLEND_FACTOR srcRGBFact = EBF_ZERO; - E_BLEND_FACTOR dstRGBFact = EBF_ZERO; - E_BLEND_FACTOR srcAlphaFact = EBF_ZERO; - E_BLEND_FACTOR dstAlphaFact = EBF_ZERO; - E_MODULATE_FUNC modulo = EMFN_MODULATE_1X; - u32 alphaSource = 0; - - unpack_textureBlendFuncSeparate(srcRGBFact, dstRGBFact, srcAlphaFact, dstAlphaFact, modulo, alphaSource, material.BlendFactor); - - if (queryFeature(EVDF_BLEND_SEPARATE)) { - CacheHandler->setBlendFuncSeparate(getGLBlend(srcRGBFact), getGLBlend(dstRGBFact), - getGLBlend(srcAlphaFact), getGLBlend(dstAlphaFact)); - } else { - CacheHandler->setBlendFunc(getGLBlend(srcRGBFact), getGLBlend(dstRGBFact)); - } - } - - // TODO: Polygon Offset. Not sure if it was left out deliberately or if it won't work with this driver. - - // thickness - if (resetAllRenderStates || lastmaterial.Thickness != material.Thickness) { - if (AntiAlias) { - // glPointSize(core::clamp(static_cast(material.Thickness), DimSmoothedPoint[0], DimSmoothedPoint[1])); - // we don't use point smoothing - glPointSize(core::clamp(static_cast(material.Thickness), DimAliasedPoint[0], DimAliasedPoint[1])); - } else { - glPointSize(core::clamp(static_cast(material.Thickness), DimAliasedPoint[0], DimAliasedPoint[1])); - glLineWidth(core::clamp(static_cast(material.Thickness), DimAliasedLine[0], DimAliasedLine[1])); - } - } - - // Anti aliasing - if (resetAllRenderStates || lastmaterial.AntiAliasing != material.AntiAliasing) { - if (material.AntiAliasing & EAAM_ALPHA_TO_COVERAGE) - glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); - else if (lastmaterial.AntiAliasing & EAAM_ALPHA_TO_COVERAGE) - glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); - - if ((AntiAlias >= 2) && (material.AntiAliasing & (EAAM_SIMPLE | EAAM_QUALITY))) - glEnable(GL_MULTISAMPLE); - else - glDisable(GL_MULTISAMPLE); - } - - // Texture parameters - setTextureRenderStates(material, resetAllRenderStates); -} - -//! Compare in SMaterial doesn't check texture parameters, so we should call this on each OnRender call. -void COGLES1Driver::setTextureRenderStates(const SMaterial &material, bool resetAllRenderstates) -{ - // Set textures to TU/TIU and apply filters to them - - for (s32 i = Feature.MaxTextureUnits - 1; i >= 0; --i) { - CacheHandler->getTextureCache().set(i, material.TextureLayers[i].Texture); - - const COGLES1Texture *tmpTexture = CacheHandler->getTextureCache().get(i); - - if (!tmpTexture) - continue; - - GLenum tmpTextureType = tmpTexture->getOpenGLTextureType(); - - CacheHandler->setActiveTexture(GL_TEXTURE0 + i); - - { - const bool isRTT = tmpTexture->isRenderTarget(); - - glMatrixMode(GL_TEXTURE); - - if (!isRTT && Matrices[ETS_TEXTURE_0 + i].isIdentity()) - glLoadIdentity(); - else { - GLfloat glmat[16]; - if (isRTT) - getGLTextureMatrix(glmat, Matrices[ETS_TEXTURE_0 + i] * TextureFlipMatrix); - else - getGLTextureMatrix(glmat, Matrices[ETS_TEXTURE_0 + i]); - glLoadMatrixf(glmat); - } - } - - COGLES1Texture::SStatesCache &statesCache = tmpTexture->getStatesCache(); - - if (resetAllRenderstates) - statesCache.IsCached = false; - -#if defined(GL_EXT_texture_lod_bias) - if (FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_EXT_texture_lod_bias]) { - if (material.TextureLayers[i].LODBias) { - const float tmp = core::clamp(material.TextureLayers[i].LODBias * 0.125f, -MaxTextureLODBias, MaxTextureLODBias); - glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, tmp); - } else - glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, 0.f); - } -#endif - - if (!statesCache.IsCached || material.TextureLayers[i].MagFilter != statesCache.MagFilter) { - E_TEXTURE_MAG_FILTER magFilter = material.TextureLayers[i].MagFilter; - glTexParameteri(tmpTextureType, GL_TEXTURE_MAG_FILTER, - magFilter == ETMAGF_NEAREST ? GL_NEAREST : (assert(magFilter == ETMAGF_LINEAR), GL_LINEAR)); - - statesCache.MagFilter = magFilter; - } - - if (material.UseMipMaps && tmpTexture->hasMipMaps()) { - if (!statesCache.IsCached || material.TextureLayers[i].MinFilter != statesCache.MinFilter || - !statesCache.MipMapStatus) { - E_TEXTURE_MIN_FILTER minFilter = material.TextureLayers[i].MinFilter; - glTexParameteri(tmpTextureType, GL_TEXTURE_MIN_FILTER, - minFilter == ETMINF_NEAREST_MIPMAP_NEAREST ? GL_NEAREST_MIPMAP_NEAREST : minFilter == ETMINF_LINEAR_MIPMAP_NEAREST ? GL_LINEAR_MIPMAP_NEAREST - : minFilter == ETMINF_NEAREST_MIPMAP_LINEAR ? GL_NEAREST_MIPMAP_LINEAR - : (assert(minFilter == ETMINF_LINEAR_MIPMAP_LINEAR), GL_LINEAR_MIPMAP_LINEAR)); - - statesCache.MinFilter = minFilter; - statesCache.MipMapStatus = true; - } - } else { - if (!statesCache.IsCached || material.TextureLayers[i].MinFilter != statesCache.MinFilter || - statesCache.MipMapStatus) { - E_TEXTURE_MIN_FILTER minFilter = material.TextureLayers[i].MinFilter; - glTexParameteri(tmpTextureType, GL_TEXTURE_MIN_FILTER, - (minFilter == ETMINF_NEAREST_MIPMAP_NEAREST || minFilter == ETMINF_NEAREST_MIPMAP_LINEAR) ? GL_NEAREST : (assert(minFilter == ETMINF_LINEAR_MIPMAP_NEAREST || minFilter == ETMINF_LINEAR_MIPMAP_LINEAR), GL_LINEAR)); - - statesCache.MinFilter = minFilter; - statesCache.MipMapStatus = false; - } - } - -#ifdef GL_EXT_texture_filter_anisotropic - if (FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_EXT_texture_filter_anisotropic] && - (!statesCache.IsCached || material.TextureLayers[i].AnisotropicFilter != statesCache.AnisotropicFilter)) { - glTexParameteri(tmpTextureType, GL_TEXTURE_MAX_ANISOTROPY_EXT, - material.TextureLayers[i].AnisotropicFilter > 1 ? core::min_(MaxAnisotropy, material.TextureLayers[i].AnisotropicFilter) : 1); - - statesCache.AnisotropicFilter = material.TextureLayers[i].AnisotropicFilter; - } -#endif - - if (!statesCache.IsCached || material.TextureLayers[i].TextureWrapU != statesCache.WrapU) { - glTexParameteri(tmpTextureType, GL_TEXTURE_WRAP_S, getTextureWrapMode(material.TextureLayers[i].TextureWrapU)); - statesCache.WrapU = material.TextureLayers[i].TextureWrapU; - } - - if (!statesCache.IsCached || material.TextureLayers[i].TextureWrapV != statesCache.WrapV) { - glTexParameteri(tmpTextureType, GL_TEXTURE_WRAP_T, getTextureWrapMode(material.TextureLayers[i].TextureWrapV)); - statesCache.WrapV = material.TextureLayers[i].TextureWrapV; - } - - statesCache.IsCached = true; - } - - // be sure to leave in texture stage 0 - CacheHandler->setActiveTexture(GL_TEXTURE0); -} - -//! sets the needed renderstates -void COGLES1Driver::setRenderStates2DMode(bool alpha, bool texture, bool alphaChannel) -{ - if (CurrentRenderMode != ERM_2D || Transformation3DChanged) { - // unset last 3d material - if (CurrentRenderMode == ERM_3D) { - if (static_cast(LastMaterial.MaterialType) < MaterialRenderers.size()) - MaterialRenderers[LastMaterial.MaterialType].Renderer->OnUnsetMaterial(); - } - if (Transformation3DChanged) { - glMatrixMode(GL_PROJECTION); - - const core::dimension2d &renderTargetSize = getCurrentRenderTargetSize(); - core::matrix4 m(core::matrix4::EM4CONST_NOTHING); - m.buildProjectionMatrixOrthoLH(f32(renderTargetSize.Width), f32(-(s32)(renderTargetSize.Height)), -1.0f, 1.0f); - m.setTranslation(core::vector3df(-1, 1, 0)); - glLoadMatrixf(m.pointer()); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - Transformation3DChanged = false; - } - } - - Material = (OverrideMaterial2DEnabled) ? OverrideMaterial2D : InitMaterial2D; - Material.Lighting = false; - Material.TextureLayers[0].Texture = (texture) ? const_cast(CacheHandler->getTextureCache().get(0)) : 0; - setTransform(ETS_TEXTURE_0, core::IdentityMatrix); - - setBasicRenderStates(Material, LastMaterial, false); - - LastMaterial = Material; - CacheHandler->correctCacheMaterial(LastMaterial); - - // no alphaChannel without texture - alphaChannel &= texture; - - if (alphaChannel || alpha) { - CacheHandler->setBlend(true); - CacheHandler->setBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - CacheHandler->setBlendEquation(GL_FUNC_ADD); - glEnable(GL_ALPHA_TEST); - glAlphaFunc(GL_GREATER, 0.f); - } else { - CacheHandler->setBlend(false); - glDisable(GL_ALPHA_TEST); - } - - if (texture) { - // Due to the transformation change, the previous line would call a reset each frame - // but we can safely reset the variable as it was false before - Transformation3DChanged = false; - - if (alphaChannel) { - // if alpha and alpha texture just modulate, otherwise use only the alpha channel - if (alpha) { - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - } else { - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE); - // rgb always modulates - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR); - } - } else { - if (alpha) { - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PRIMARY_COLOR); - // rgb always modulates - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR); - } else { - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - } - } - } - - CurrentRenderMode = ERM_2D; -} - -//! \return Returns the name of the video driver. -const char *COGLES1Driver::getName() const -{ - return Name.c_str(); -} - -//! Sets the dynamic ambient light color. -void COGLES1Driver::setAmbientLight(const SColorf &color) -{ - CNullDriver::setAmbientLight(color); - GLfloat data[4] = {color.r, color.g, color.b, color.a}; - glLightModelfv(GL_LIGHT_MODEL_AMBIENT, data); -} - -// this code was sent in by Oliver Klems, thank you -void COGLES1Driver::setViewPort(const core::rect &area) -{ - core::rect vp = area; - core::rect rendert(0, 0, getCurrentRenderTargetSize().Width, getCurrentRenderTargetSize().Height); - vp.clipAgainst(rendert); - - if (vp.getHeight() > 0 && vp.getWidth() > 0) - CacheHandler->setViewport(vp.UpperLeftCorner.X, getCurrentRenderTargetSize().Height - vp.UpperLeftCorner.Y - vp.getHeight(), vp.getWidth(), vp.getHeight()); - - ViewPort = vp; -} - -void COGLES1Driver::setViewPortRaw(u32 width, u32 height) -{ - CacheHandler->setViewport(0, 0, width, height); - ViewPort = core::recti(0, 0, width, height); -} - -//! Sets the fog mode. -void COGLES1Driver::setFog(SColor c, E_FOG_TYPE fogType, f32 start, - f32 end, f32 density, bool pixelFog, bool rangeFog) -{ - CNullDriver::setFog(c, fogType, start, end, density, pixelFog, rangeFog); - - glFogf(GL_FOG_MODE, GLfloat((fogType == EFT_FOG_LINEAR) ? GL_LINEAR : (fogType == EFT_FOG_EXP) ? GL_EXP - : GL_EXP2)); - -#ifdef GL_EXT_fog_coord - if (FeatureAvailable[IRR_EXT_fog_coord]) - glFogi(GL_FOG_COORDINATE_SOURCE, GL_FRAGMENT_DEPTH); -#endif - - if (fogType == EFT_FOG_LINEAR) { - glFogf(GL_FOG_START, start); - glFogf(GL_FOG_END, end); - } else - glFogf(GL_FOG_DENSITY, density); - - if (pixelFog) - glHint(GL_FOG_HINT, GL_NICEST); - else - glHint(GL_FOG_HINT, GL_FASTEST); - - SColorf color(c); - GLfloat data[4] = {color.r, color.g, color.b, color.a}; - glFogfv(GL_FOG_COLOR, data); -} - -//! Draws a 3d line. -void COGLES1Driver::draw3DLine(const core::vector3df &start, - const core::vector3df &end, SColor color) -{ - setRenderStates3DMode(); - - u16 indices[] = {0, 1}; - S3DVertex vertices[2]; - vertices[0] = S3DVertex(start.X, start.Y, start.Z, 0, 0, 1, color, 0, 0); - vertices[1] = S3DVertex(end.X, end.Y, end.Z, 0, 0, 1, color, 0, 0); - drawVertexPrimitiveList2d3d(vertices, 2, indices, 1, video::EVT_STANDARD, scene::EPT_LINES); -} - -//! Only used by the internal engine. Used to notify the driver that -//! the window was resized. -void COGLES1Driver::OnResize(const core::dimension2d &size) -{ - CNullDriver::OnResize(size); - CacheHandler->setViewport(0, 0, size.Width, size.Height); - Transformation3DChanged = true; -} - -//! Returns type of video driver -E_DRIVER_TYPE COGLES1Driver::getDriverType() const -{ - return EDT_OGLES1; -} - -//! returns color format -ECOLOR_FORMAT COGLES1Driver::getColorFormat() const -{ - return ColorFormat; -} - -//! Get a vertex shader constant index. -s32 COGLES1Driver::getVertexShaderConstantID(const c8 *name) -{ - return getPixelShaderConstantID(name); -} - -//! Get a pixel shader constant index. -s32 COGLES1Driver::getPixelShaderConstantID(const c8 *name) -{ - os::Printer::log("Error: Please use IMaterialRendererServices from IShaderConstantSetCallBack::OnSetConstants not VideoDriver->getPixelShaderConstantID()."); - return -1; -} - -//! Sets a constant for the vertex shader based on an index. -bool COGLES1Driver::setVertexShaderConstant(s32 index, const f32 *floats, int count) -{ - // pass this along, as in GLSL the same routine is used for both vertex and fragment shaders - return setPixelShaderConstant(index, floats, count); -} - -//! Int interface for the above. -bool COGLES1Driver::setVertexShaderConstant(s32 index, const s32 *ints, int count) -{ - return setPixelShaderConstant(index, ints, count); -} - -bool COGLES1Driver::setVertexShaderConstant(s32 index, const u32 *ints, int count) -{ - return setPixelShaderConstant(index, ints, count); -} - -//! Sets a constant for the pixel shader based on an index. -bool COGLES1Driver::setPixelShaderConstant(s32 index, const f32 *floats, int count) -{ - os::Printer::log("Error: Please use IMaterialRendererServices from IShaderConstantSetCallBack::OnSetConstants not VideoDriver->setPixelShaderConstant()."); - return false; -} - -//! Int interface for the above. -bool COGLES1Driver::setPixelShaderConstant(s32 index, const s32 *ints, int count) -{ - os::Printer::log("Error: Please use IMaterialRendererServices from IShaderConstantSetCallBack::OnSetConstants not VideoDriver->setPixelShaderConstant()."); - return false; -} - -bool COGLES1Driver::setPixelShaderConstant(s32 index, const u32 *ints, int count) -{ - os::Printer::log("Error: Please use IMaterialRendererServices from IShaderConstantSetCallBack::OnSetConstants not VideoDriver->setPixelShaderConstant()."); - return false; -} - -//! Adds a new material renderer to the VideoDriver, using GLSL to render geometry. -s32 COGLES1Driver::addHighLevelShaderMaterial( - const c8 *vertexShaderProgram, - const c8 *vertexShaderEntryPointName, - E_VERTEX_SHADER_TYPE vsCompileTarget, - const c8 *pixelShaderProgram, - const c8 *pixelShaderEntryPointName, - E_PIXEL_SHADER_TYPE psCompileTarget, - const c8 *geometryShaderProgram, - const c8 *geometryShaderEntryPointName, - E_GEOMETRY_SHADER_TYPE gsCompileTarget, - scene::E_PRIMITIVE_TYPE inType, - scene::E_PRIMITIVE_TYPE outType, - u32 verticesOut, - IShaderConstantSetCallBack *callback, - E_MATERIAL_TYPE baseMaterial, - s32 userData) -{ - os::Printer::log("No shader support."); - return -1; -} - -//! Returns a pointer to the IVideoDriver interface. (Implementation for -//! IMaterialRendererServices) -IVideoDriver *COGLES1Driver::getVideoDriver() -{ - return this; -} - -//! Returns pointer to the IGPUProgrammingServices interface. -IGPUProgrammingServices *COGLES1Driver::getGPUProgrammingServices() -{ - return this; -} - -ITexture *COGLES1Driver::addRenderTargetTexture(const core::dimension2d &size, - const io::path &name, const ECOLOR_FORMAT format) -{ - // disable mip-mapping - bool generateMipLevels = getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); - setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, false); - - bool supportForFBO = (Feature.ColorAttachment > 0); - - core::dimension2du destSize(size); - - if (!supportForFBO) { - destSize = core::dimension2d(core::min_(size.Width, ScreenSize.Width), core::min_(size.Height, ScreenSize.Height)); - destSize = destSize.getOptimalSize((size == size.getOptimalSize()), false, false); - } - - COGLES1Texture *renderTargetTexture = new COGLES1Texture(name, destSize, ETT_2D, format, this); - addTexture(renderTargetTexture); - renderTargetTexture->drop(); - - // restore mip-mapping - setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, generateMipLevels); - - return renderTargetTexture; -} - -ITexture *COGLES1Driver::addRenderTargetTextureCubemap(const irr::u32 sideLen, const io::path &name, const ECOLOR_FORMAT format) -{ - // disable mip-mapping - bool generateMipLevels = getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); - setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, false); - - bool supportForFBO = (Feature.ColorAttachment > 0); - - const core::dimension2d size(sideLen, sideLen); - core::dimension2du destSize(size); - - if (!supportForFBO) { - destSize = core::dimension2d(core::min_(size.Width, ScreenSize.Width), core::min_(size.Height, ScreenSize.Height)); - destSize = destSize.getOptimalSize((size == size.getOptimalSize()), false, false); - } - - COGLES1Texture *renderTargetTexture = new COGLES1Texture(name, destSize, ETT_CUBEMAP, format, this); - addTexture(renderTargetTexture); - renderTargetTexture->drop(); - - // restore mip-mapping - setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, generateMipLevels); - - return renderTargetTexture; -} - -//! Returns the maximum amount of primitives -u32 COGLES1Driver::getMaximalPrimitiveCount() const -{ - return 65535; -} - -bool COGLES1Driver::setRenderTargetEx(IRenderTarget *target, u16 clearFlag, SColor clearColor, f32 clearDepth, u8 clearStencil) -{ - if (target && target->getDriverType() != EDT_OGLES1) { - os::Printer::log("Fatal Error: Tried to set a render target not owned by OpenGL driver.", ELL_ERROR); - return false; - } - - bool supportForFBO = (Feature.ColorAttachment > 0); - - core::dimension2d destRenderTargetSize(0, 0); - - if (target) { - COGLES1RenderTarget *renderTarget = static_cast(target); - - if (supportForFBO) { - CacheHandler->setFBO(renderTarget->getBufferID()); - renderTarget->update(); - } - - destRenderTargetSize = renderTarget->getSize(); - - setViewPortRaw(destRenderTargetSize.Width, destRenderTargetSize.Height); - } else { - if (supportForFBO) - CacheHandler->setFBO(0); - else { - COGLES1RenderTarget *prevRenderTarget = static_cast(CurrentRenderTarget); - COGLES1Texture *renderTargetTexture = static_cast(prevRenderTarget->getTexture()); - - if (renderTargetTexture) { - const COGLES1Texture *prevTexture = CacheHandler->getTextureCache().get(0); - - CacheHandler->getTextureCache().set(0, renderTargetTexture); - - const core::dimension2d size = renderTargetTexture->getSize(); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, size.Width, size.Height); - - CacheHandler->getTextureCache().set(0, prevTexture); - } - } - - destRenderTargetSize = core::dimension2d(0, 0); - - setViewPortRaw(ScreenSize.Width, ScreenSize.Height); - } - - if (CurrentRenderTargetSize != destRenderTargetSize) { - CurrentRenderTargetSize = destRenderTargetSize; - - Transformation3DChanged = true; - } - - CurrentRenderTarget = target; - - if (!supportForFBO) { - clearFlag |= ECBF_COLOR; - clearFlag |= ECBF_DEPTH; - } - - clearBuffers(clearFlag, clearColor, clearDepth, clearStencil); - - return true; -} - -void COGLES1Driver::clearBuffers(u16 flag, SColor color, f32 depth, u8 stencil) -{ - GLbitfield mask = 0; - - if (flag & ECBF_COLOR) { - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - - const f32 inv = 1.0f / 255.0f; - glClearColor(color.getRed() * inv, color.getGreen() * inv, - color.getBlue() * inv, color.getAlpha() * inv); - - mask |= GL_COLOR_BUFFER_BIT; - } - - if (flag & ECBF_DEPTH) { - glDepthMask(GL_TRUE); - glClearDepthf(depth); - mask |= GL_DEPTH_BUFFER_BIT; - } - - if (flag & ECBF_STENCIL) { - glClearStencil(stencil); - mask |= GL_STENCIL_BUFFER_BIT; - } - - if (mask) - glClear(mask); -} - -//! Returns an image created from the last rendered frame. -// We want to read the front buffer to get the latest render finished. -// This is not possible under ogl-es, though, so one has to call this method -// outside of the render loop only. -IImage *COGLES1Driver::createScreenShot(video::ECOLOR_FORMAT format, video::E_RENDER_TARGET target) -{ - if (target == video::ERT_MULTI_RENDER_TEXTURES || target == video::ERT_RENDER_TEXTURE || target == video::ERT_STEREO_BOTH_BUFFERS) - return 0; - GLint internalformat = GL_RGBA; - GLint type = GL_UNSIGNED_BYTE; - if (false && (FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_IMG_read_format] || FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_OES_read_format] || FeatureAvailable[COGLESCoreExtensionHandler::IRR_GL_EXT_read_format_bgra])) { -#ifdef GL_IMPLEMENTATION_COLOR_READ_TYPE_OES - glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES, &internalformat); - glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES, &type); -#endif - // there are formats we don't support ATM - if (GL_UNSIGNED_SHORT_4_4_4_4 == type) - type = GL_UNSIGNED_SHORT_5_5_5_1; -#ifdef GL_EXT_read_format_bgra - else if (GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT == type) - type = GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT; -#endif - } - - IImage *newImage = 0; - if ((GL_RGBA == internalformat) -#ifdef GL_EXT_read_format_bgra - || (GL_BGRA_EXT == internalformat) -#endif - ) { - if (GL_UNSIGNED_BYTE == type) - newImage = new CImage(ECF_A8R8G8B8, ScreenSize); - else - newImage = new CImage(ECF_A1R5G5B5, ScreenSize); - } else { - if (GL_UNSIGNED_BYTE == type) - newImage = new CImage(ECF_R8G8B8, ScreenSize); - else - newImage = new CImage(ECF_R5G6B5, ScreenSize); - } - - u8 *pixels = static_cast(newImage->getData()); - if (!pixels) { - newImage->drop(); - return 0; - } - - glReadPixels(0, 0, ScreenSize.Width, ScreenSize.Height, internalformat, type, pixels); - - // opengl images are horizontally flipped, so we have to fix that here. - const s32 pitch = newImage->getPitch(); - u8 *p2 = pixels + (ScreenSize.Height - 1) * pitch; - u8 *tmpBuffer = new u8[pitch]; - for (u32 i = 0; i < ScreenSize.Height; i += 2) { - memcpy(tmpBuffer, pixels, pitch); - memcpy(pixels, p2, pitch); - memcpy(p2, tmpBuffer, pitch); - pixels += pitch; - p2 -= pitch; - } - delete[] tmpBuffer; - - if (testGLError(__LINE__)) { - newImage->drop(); - return 0; - } - - return newImage; -} - -void COGLES1Driver::removeTexture(ITexture *texture) -{ - CacheHandler->getTextureCache().remove(texture); - CNullDriver::removeTexture(texture); -} - -core::dimension2du COGLES1Driver::getMaxTextureSize() const -{ - return core::dimension2du(MaxTextureSize, MaxTextureSize); -} - -GLenum COGLES1Driver::getGLBlend(E_BLEND_FACTOR factor) const -{ - static GLenum const blendTable[] = { - GL_ZERO, - GL_ONE, - GL_DST_COLOR, - GL_ONE_MINUS_DST_COLOR, - GL_SRC_COLOR, - GL_ONE_MINUS_SRC_COLOR, - GL_SRC_ALPHA, - GL_ONE_MINUS_SRC_ALPHA, - GL_DST_ALPHA, - GL_ONE_MINUS_DST_ALPHA, - GL_SRC_ALPHA_SATURATE, - }; - - return blendTable[factor]; -} - -GLenum COGLES1Driver::getZBufferBits() const -{ - GLenum bits = 0; - - switch (Params.ZBufferBits) { - case 24: -#if defined(GL_OES_depth24) - if (queryGLESFeature(COGLESCoreExtensionHandler::IRR_GL_OES_depth24)) - bits = GL_DEPTH_COMPONENT24_OES; - else -#endif - bits = GL_DEPTH_COMPONENT16; - break; - case 32: -#if defined(GL_OES_depth32) - if (queryGLESFeature(COGLESCoreExtensionHandler::IRR_GL_OES_depth32)) - bits = GL_DEPTH_COMPONENT32_OES; - else -#endif - bits = GL_DEPTH_COMPONENT16; - break; - default: - bits = GL_DEPTH_COMPONENT16; - break; - } - - return bits; -} - -bool COGLES1Driver::getColorFormatParameters(ECOLOR_FORMAT format, GLint &internalFormat, GLenum &pixelFormat, - GLenum &pixelType, void (**converter)(const void *, s32, void *)) const -{ - bool supported = false; - internalFormat = GL_RGBA; - pixelFormat = GL_RGBA; - pixelType = GL_UNSIGNED_BYTE; - *converter = 0; - - switch (format) { - case ECF_A1R5G5B5: - supported = true; - internalFormat = GL_RGBA; - pixelFormat = GL_RGBA; - pixelType = GL_UNSIGNED_SHORT_5_5_5_1; - *converter = CColorConverter::convert_A1R5G5B5toR5G5B5A1; - break; - case ECF_R5G6B5: - supported = true; - internalFormat = GL_RGB; - pixelFormat = GL_RGB; - pixelType = GL_UNSIGNED_SHORT_5_6_5; - break; - case ECF_R8G8B8: - supported = true; - internalFormat = GL_RGB; - pixelFormat = GL_RGB; - pixelType = GL_UNSIGNED_BYTE; - break; - case ECF_A8R8G8B8: - supported = true; - if (queryGLESFeature(COGLESCoreExtensionHandler::IRR_GL_IMG_texture_format_BGRA8888) || - queryGLESFeature(COGLESCoreExtensionHandler::IRR_GL_EXT_texture_format_BGRA8888) || - queryGLESFeature(COGLESCoreExtensionHandler::IRR_GL_APPLE_texture_format_BGRA8888)) { - internalFormat = GL_BGRA; - pixelFormat = GL_BGRA; - } else { - internalFormat = GL_RGBA; - pixelFormat = GL_RGBA; - *converter = CColorConverter::convert_A8R8G8B8toA8B8G8R8; - } - pixelType = GL_UNSIGNED_BYTE; - break; - case ECF_D16: - supported = true; - internalFormat = GL_DEPTH_COMPONENT16; - pixelFormat = GL_DEPTH_COMPONENT; - pixelType = GL_UNSIGNED_SHORT; - break; - case ECF_D32: -#if defined(GL_OES_depth32) - if (queryGLESFeature(COGLESCoreExtensionHandler::IRR_GL_OES_depth32)) { - supported = true; - internalFormat = GL_DEPTH_COMPONENT32_OES; - pixelFormat = GL_DEPTH_COMPONENT; - pixelType = GL_UNSIGNED_INT; - } -#endif - break; - case ECF_D24S8: -#ifdef GL_OES_packed_depth_stencil - if (queryGLESFeature(COGLESCoreExtensionHandler::IRR_GL_OES_packed_depth_stencil)) { - supported = true; - internalFormat = GL_DEPTH24_STENCIL8_OES; - pixelFormat = GL_DEPTH_STENCIL_OES; - pixelType = GL_UNSIGNED_INT_24_8_OES; - } -#endif - break; - case ECF_R8: - break; - case ECF_R8G8: - break; - case ECF_R16: - break; - case ECF_R16G16: - break; - case ECF_R16F: - break; - case ECF_G16R16F: - break; - case ECF_A16B16G16R16F: - break; - case ECF_R32F: - break; - case ECF_G32R32F: - break; - case ECF_A32B32G32R32F: - break; - default: - break; - } - -#ifdef _IRR_IOS_PLATFORM_ - if (internalFormat == GL_BGRA) - internalFormat = GL_RGBA; -#endif - - return supported; -} - -bool COGLES1Driver::queryTextureFormat(ECOLOR_FORMAT format) const -{ - GLint dummyInternalFormat; - GLenum dummyPixelFormat; - GLenum dummyPixelType; - void (*dummyConverter)(const void *, s32, void *); - return getColorFormatParameters(format, dummyInternalFormat, dummyPixelFormat, dummyPixelType, &dummyConverter); -} - -bool COGLES1Driver::needsTransparentRenderPass(const irr::video::SMaterial &material) const -{ - return CNullDriver::needsTransparentRenderPass(material) || material.isAlphaBlendOperation(); -} - -COGLES1CacheHandler *COGLES1Driver::getCacheHandler() const -{ - return CacheHandler; -} - -} // end namespace -} // end namespace - -#endif // _IRR_COMPILE_WITH_OGLES1_ - -namespace irr -{ -namespace video -{ - -#ifndef _IRR_COMPILE_WITH_OGLES1_ -class IVideoDriver; -class IContextManager; -#endif - -IVideoDriver *createOGLES1Driver(const SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager) -{ -#ifdef _IRR_COMPILE_WITH_OGLES1_ - return new COGLES1Driver(params, io, contextManager); -#else - return 0; -#endif // _IRR_COMPILE_WITH_OGLES1_ -} - -} // end namespace -} // end namespace diff --git a/irr/src/COGLESDriver.h b/irr/src/COGLESDriver.h deleted file mode 100644 index 8b9152cea..000000000 --- a/irr/src/COGLESDriver.h +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (C) 2002-20014 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in Irrlicht.h - -#pragma once - -#include "SIrrCreationParameters.h" - -#ifdef _IRR_COMPILE_WITH_OGLES1_ - -#include "CNullDriver.h" -#include "IMaterialRendererServices.h" -#include "EDriverFeatures.h" -#include "fast_atof.h" -#include "COGLESExtensionHandler.h" -#include "IContextManager.h" - -#define TEST_GL_ERROR(cls) (cls)->testGLError(__LINE__) - -namespace irr -{ -namespace video -{ - -class COGLES1Driver : public CNullDriver, public IMaterialRendererServices, public COGLES1ExtensionHandler -{ - friend class COpenGLCoreTexture; - -public: - //! constructor - COGLES1Driver(const SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager); - - //! destructor - virtual ~COGLES1Driver(); - - virtual bool beginScene(u16 clearFlag, SColor clearColor = SColor(255, 0, 0, 0), f32 clearDepth = 1.f, u8 clearStencil = 0, - const SExposedVideoData &videoData = SExposedVideoData(), core::rect *sourceRect = 0) override; - - bool endScene() override; - - //! sets transformation - void setTransform(E_TRANSFORMATION_STATE state, const core::matrix4 &mat) override; - - struct SHWBufferLink_opengl : public SHWBufferLink - { - SHWBufferLink_opengl(const scene::IMeshBuffer *_MeshBuffer) : - SHWBufferLink(_MeshBuffer), vbo_verticesID(0), vbo_indicesID(0) {} - - GLuint vbo_verticesID; // tmp - GLuint vbo_indicesID; // tmp - - GLuint vbo_verticesSize; // tmp - GLuint vbo_indicesSize; // tmp - }; - - bool updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuffer); - bool updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffer); - - //! updates hardware buffer if needed - bool updateHardwareBuffer(SHWBufferLink *HWBuffer) override; - - //! Create hardware buffer from mesh - SHWBufferLink *createHardwareBuffer(const scene::IMeshBuffer *mb) override; - - //! Delete hardware buffer (only some drivers can) - void deleteHardwareBuffer(SHWBufferLink *HWBuffer) override; - - //! Draw hardware buffer - void drawHardwareBuffer(SHWBufferLink *HWBuffer) override; - - IRenderTarget *addRenderTarget() override; - - //! draws a vertex primitive list - virtual void drawVertexPrimitiveList(const void *vertices, u32 vertexCount, - const void *indexList, u32 primitiveCount, - E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType) override; - - void drawVertexPrimitiveList2d3d(const void *vertices, u32 vertexCount, const void *indexList, u32 primitiveCount, E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType = EIT_16BIT, bool threed = true); - - //! queries the features of the driver, returns true if feature is available - bool queryFeature(E_VIDEO_DRIVER_FEATURE feature) const override - { - // return FeatureEnabled[feature] && COGLES1ExtensionHandler::queryFeature(feature); - return COGLES1ExtensionHandler::queryFeature(feature); - } - - //! Sets a material. - void setMaterial(const SMaterial &material) override; - - virtual void draw2DImage(const video::ITexture *texture, const core::position2d &destPos, - const core::rect &sourceRect, const core::rect *clipRect = 0, - SColor color = SColor(255, 255, 255, 255), bool useAlphaChannelOfTexture = false) override; - - virtual void draw2DImage(const video::ITexture *texture, const core::rect &destRect, - const core::rect &sourceRect, const core::rect *clipRect = 0, - const video::SColor *const colors = 0, bool useAlphaChannelOfTexture = false) override; - - virtual void draw2DImage(const video::ITexture *texture, u32 layer, bool flip); - - //! draws a set of 2d images, using a color and the alpha channel of the texture if desired. - virtual void draw2DImageBatch(const video::ITexture *texture, - const core::array> &positions, - const core::array> &sourceRects, - const core::rect *clipRect = 0, - SColor color = SColor(255, 255, 255, 255), - bool useAlphaChannelOfTexture = false) override; - - //! draw an 2d rectangle - virtual void draw2DRectangle(SColor color, const core::rect &pos, - const core::rect *clip = 0) override; - - //! Draws an 2d rectangle with a gradient. - virtual void draw2DRectangle(const core::rect &pos, - SColor colorLeftUp, SColor colorRightUp, SColor colorLeftDown, SColor colorRightDown, - const core::rect *clip = 0) override; - - //! Draws a 2d line. - virtual void draw2DLine(const core::position2d &start, - const core::position2d &end, - SColor color = SColor(255, 255, 255, 255)) override; - - //! Draws a 3d line. - virtual void draw3DLine(const core::vector3df &start, - const core::vector3df &end, - SColor color = SColor(255, 255, 255, 255)) override; - - //! Returns the name of the video driver. - const char *getName() const override; - - //! Sets the dynamic ambient light color. - void setAmbientLight(const SColorf &color) override; - - //! sets a viewport - void setViewPort(const core::rect &area) override; - - //! Sets the fog mode. - virtual void setFog(SColor color, E_FOG_TYPE fogType, f32 start, - f32 end, f32 density, bool pixelFog, bool rangeFog) override; - - //! Only used internally by the engine - void OnResize(const core::dimension2d &size) override; - - //! Returns type of video driver - E_DRIVER_TYPE getDriverType() const override; - - //! get color format of the current color buffer - ECOLOR_FORMAT getColorFormat() const override; - - //! Returns the transformation set by setTransform - const core::matrix4 &getTransform(E_TRANSFORMATION_STATE state) const override; - - //! Can be called by an IMaterialRenderer to make its work easier. - virtual void setBasicRenderStates(const SMaterial &material, const SMaterial &lastmaterial, - bool resetAllRenderstates) override; - - //! Compare in SMaterial doesn't check texture parameters, so we should call this on each OnRender call. - virtual void setTextureRenderStates(const SMaterial &material, bool resetAllRenderstates); - - //! Get a vertex shader constant index. - s32 getVertexShaderConstantID(const c8 *name) override; - - //! Get a pixel shader constant index. - s32 getPixelShaderConstantID(const c8 *name) override; - - //! Sets a constant for the vertex shader based on an index. - bool setVertexShaderConstant(s32 index, const f32 *floats, int count) override; - - //! Int interface for the above. - bool setVertexShaderConstant(s32 index, const s32 *ints, int count) override; - - //! Uint interface for the above. - bool setVertexShaderConstant(s32 index, const u32 *ints, int count) override; - - //! Sets a constant for the pixel shader based on an index. - bool setPixelShaderConstant(s32 index, const f32 *floats, int count) override; - - //! Int interface for the above. - bool setPixelShaderConstant(s32 index, const s32 *ints, int count) override; - - //! Uint interface for the above. - bool setPixelShaderConstant(s32 index, const u32 *ints, int count) override; - - //! Adds a new material renderer to the VideoDriver - virtual s32 addHighLevelShaderMaterial(const c8 *vertexShaderProgram, const c8 *vertexShaderEntryPointName, - E_VERTEX_SHADER_TYPE vsCompileTarget, const c8 *pixelShaderProgram, const c8 *pixelShaderEntryPointName, - E_PIXEL_SHADER_TYPE psCompileTarget, const c8 *geometryShaderProgram, const c8 *geometryShaderEntryPointName, - E_GEOMETRY_SHADER_TYPE gsCompileTarget, scene::E_PRIMITIVE_TYPE inType, scene::E_PRIMITIVE_TYPE outType, - u32 verticesOut, IShaderConstantSetCallBack *callback, E_MATERIAL_TYPE baseMaterial, - s32 userData) override; - - //! Returns pointer to the IGPUProgrammingServices interface. - IGPUProgrammingServices *getGPUProgrammingServices() override; - - //! Returns a pointer to the IVideoDriver interface. - IVideoDriver *getVideoDriver() override; - - //! Returns the maximum amount of primitives - u32 getMaximalPrimitiveCount() const override; - - virtual ITexture *addRenderTargetTexture(const core::dimension2d &size, - const io::path &name, const ECOLOR_FORMAT format = ECF_UNKNOWN) override; - - //! Creates a render target texture for a cubemap - ITexture *addRenderTargetTextureCubemap(const irr::u32 sideLen, - const io::path &name, const ECOLOR_FORMAT format) override; - - virtual bool setRenderTargetEx(IRenderTarget *target, u16 clearFlag, SColor clearColor = SColor(255, 0, 0, 0), - f32 clearDepth = 1.f, u8 clearStencil = 0) override; - - void clearBuffers(u16 flag, SColor color = SColor(255, 0, 0, 0), f32 depth = 1.f, u8 stencil = 0) override; - - //! Returns an image created from the last rendered frame. - IImage *createScreenShot(video::ECOLOR_FORMAT format = video::ECF_UNKNOWN, video::E_RENDER_TARGET target = video::ERT_FRAME_BUFFER) override; - - //! checks if an OpenGL error has happened and prints it (+ some internal code which is usually the line number) - bool testGLError(int code = 0); - - //! Returns the graphics card vendor name. - core::stringc getVendorInfo() override - { - return VendorName; - } - - //! Get the maximal texture size for this driver - core::dimension2du getMaxTextureSize() const override; - - void removeTexture(ITexture *texture) override; - - //! Check if the driver supports creating textures with the given color format - bool queryTextureFormat(ECOLOR_FORMAT format) const override; - - //! Used by some SceneNodes to check if a material should be rendered in the transparent render pass - bool needsTransparentRenderPass(const irr::video::SMaterial &material) const override; - - //! Convert E_BLEND_FACTOR to OpenGL equivalent - GLenum getGLBlend(E_BLEND_FACTOR factor) const; - - //! Get ZBuffer bits. - GLenum getZBufferBits() const; - - bool getColorFormatParameters(ECOLOR_FORMAT format, GLint &internalFormat, GLenum &pixelFormat, - GLenum &pixelType, void (**converter)(const void *, s32, void *)) const; - - COGLES1CacheHandler *getCacheHandler() const; - -private: - //! inits the opengl-es driver - bool genericDriverInit(const core::dimension2d &screenSize, bool stencilBuffer); - - ITexture *createDeviceDependentTexture(const io::path &name, IImage *image) override; - - ITexture *createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) override; - - //! creates a transposed matrix in supplied GLfloat array to pass to OGLES1 - inline void getGLMatrix(GLfloat gl_matrix[16], const core::matrix4 &m); - inline void getGLTextureMatrix(GLfloat gl_matrix[16], const core::matrix4 &m); - - //! Set GL pipeline to desired texture wrap modes of the material - void setWrapMode(const SMaterial &material); - - //! Get OpenGL wrap enum from Irrlicht enum - GLint getTextureWrapMode(u8 clamp) const; - - //! sets the needed renderstates - void setRenderStates3DMode(); - - //! sets the needed renderstates - void setRenderStates2DMode(bool alpha, bool texture, bool alphaChannel); - - void createMaterialRenderers(); - - //! Assign a hardware light to the specified requested light, if any - //! free hardware lights exist. - //! \param[in] lightIndex: the index of the requesting light - void assignHardwareLight(u32 lightIndex); - - //! Same as `CacheHandler->setViewport`, but also sets `ViewPort` - virtual void setViewPortRaw(u32 width, u32 height); - - COGLES1CacheHandler *CacheHandler; - - core::stringc Name; - core::matrix4 Matrices[ETS_COUNT]; - core::array ColorBuffer; - - //! enumeration for rendering modes such as 2d and 3d for minimizing the switching of renderStates. - enum E_RENDER_MODE - { - ERM_NONE = 0, // no render state has been set yet. - ERM_2D, // 2d drawing rendermode - ERM_3D // 3d rendering mode - }; - - E_RENDER_MODE CurrentRenderMode; - //! bool to make all renderstates reset if set to true. - bool ResetRenderStates; - bool Transformation3DChanged; - u8 AntiAlias; - - SMaterial Material, LastMaterial; - - core::stringc VendorName; - - core::matrix4 TextureFlipMatrix; - - //! Color buffer format - ECOLOR_FORMAT ColorFormat; - - SIrrlichtCreationParameters Params; - - IContextManager *ContextManager; -}; - -} // end namespace video -} // end namespace irr - -#endif // _IRR_COMPILE_WITH_OGLES1_ diff --git a/irr/src/COGLESExtensionHandler.cpp b/irr/src/COGLESExtensionHandler.cpp deleted file mode 100644 index 866a984d8..000000000 --- a/irr/src/COGLESExtensionHandler.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2008 Christian Stehno -// Heavily based on the OpenGL driver implemented by Nikolaus Gebhardt -// 2017 modified by Michael Zeilfelder (unifying extension handlers) -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in Irrlicht.h - -#include "COGLESExtensionHandler.h" - -#ifdef _IRR_COMPILE_WITH_OGLES1_ - -#include "irrString.h" -#include "SMaterial.h" -#include "fast_atof.h" - -#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) -#include -#else -#include -#endif - -namespace irr -{ -namespace video -{ - -COGLES1ExtensionHandler::COGLES1ExtensionHandler() : - COGLESCoreExtensionHandler(), - MaxLights(0), pGlBlendEquationOES(0), pGlBlendFuncSeparateOES(0), - pGlBindFramebufferOES(0), pGlDeleteFramebuffersOES(0), - pGlGenFramebuffersOES(0), pGlCheckFramebufferStatusOES(0), - pGlFramebufferTexture2DOES(0), pGlGenerateMipmapOES(0) -{ -} - -void COGLES1ExtensionHandler::initExtensions() -{ - getGLVersion(); - - if (Version >= 100) - os::Printer::log("OpenGL ES driver version is 1.1.", ELL_INFORMATION); - else - os::Printer::log("OpenGL ES driver version is 1.0.", ELL_WARNING); - - getGLExtensions(); - - GLint val = 0; - - glGetIntegerv(GL_MAX_LIGHTS, &val); - MaxLights = static_cast(val); - - glGetIntegerv(GL_MAX_TEXTURE_UNITS, &val); - Feature.MaxTextureUnits = static_cast(val); - -#ifdef GL_EXT_texture_filter_anisotropic - if (FeatureAvailable[IRR_GL_EXT_texture_filter_anisotropic]) { - glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &val); - MaxAnisotropy = static_cast(val); - } -#endif -#ifdef GL_MAX_ELEMENTS_INDICES - glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &val); - MaxIndices = val; -#endif - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &val); - MaxTextureSize = static_cast(val); -#ifdef GL_EXT_texture_lod_bias - if (FeatureAvailable[IRR_GL_EXT_texture_lod_bias]) - glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS_EXT, &MaxTextureLODBias); -#endif - glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, DimAliasedLine); - glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, DimAliasedPoint); - - Feature.MaxTextureUnits = core::min_(Feature.MaxTextureUnits, static_cast(MATERIAL_MAX_TEXTURES)); - Feature.ColorAttachment = 1; - - pGlBlendEquationOES = (PFNGLBLENDEQUATIONOESPROC)eglGetProcAddress("glBlendEquationOES"); - pGlBlendFuncSeparateOES = (PFNGLBLENDFUNCSEPARATEOESPROC)eglGetProcAddress("glBlendFuncSeparateOES"); - pGlBindFramebufferOES = (PFNGLBINDFRAMEBUFFEROESPROC)eglGetProcAddress("glBindFramebufferOES"); - pGlDeleteFramebuffersOES = (PFNGLDELETEFRAMEBUFFERSOESPROC)eglGetProcAddress("glDeleteFramebuffersOES"); - pGlGenFramebuffersOES = (PFNGLGENFRAMEBUFFERSOESPROC)eglGetProcAddress("glGenFramebuffersOES"); - pGlCheckFramebufferStatusOES = (PFNGLCHECKFRAMEBUFFERSTATUSOESPROC)eglGetProcAddress("glCheckFramebufferStatusOES"); - pGlFramebufferTexture2DOES = (PFNGLFRAMEBUFFERTEXTURE2DOESPROC)eglGetProcAddress("glFramebufferTexture2DOES"); - pGlGenerateMipmapOES = (PFNGLGENERATEMIPMAPOESPROC)eglGetProcAddress("glGenerateMipmapOES"); -} - -} // end namespace video -} // end namespace irr - -#endif // _IRR_COMPILE_WITH_OGLES2_ diff --git a/irr/src/COGLESExtensionHandler.h b/irr/src/COGLESExtensionHandler.h deleted file mode 100644 index a316afaad..000000000 --- a/irr/src/COGLESExtensionHandler.h +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (C) 2008 Christian Stehno -// Heavily based on the OpenGL driver implemented by Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in Irrlicht.h - -#pragma once - -#ifdef _IRR_COMPILE_WITH_OGLES1_ - -#include "EDriverFeatures.h" -#include "irrTypes.h" -#include "os.h" - -#include "COGLESCommon.h" - -#include "COGLESCoreExtensionHandler.h" - -namespace irr -{ -namespace video -{ - -class COGLES1ExtensionHandler : public COGLESCoreExtensionHandler -{ -public: - COGLES1ExtensionHandler(); - - void initExtensions(); - - bool queryFeature(video::E_VIDEO_DRIVER_FEATURE feature) const - { - switch (feature) { - case EVDF_RENDER_TO_TARGET: - case EVDF_HARDWARE_TL: - case EVDF_MULTITEXTURE: - case EVDF_BILINEAR_FILTER: - case EVDF_MIP_MAP: - case EVDF_TEXTURE_NSQUARE: - case EVDF_STENCIL_BUFFER: - case EVDF_ALPHA_TO_COVERAGE: - case EVDF_COLOR_MASK: - case EVDF_POLYGON_OFFSET: - case EVDF_TEXTURE_MATRIX: - return true; - case EVDF_TEXTURE_NPOT: - return FeatureAvailable[IRR_GL_APPLE_texture_2D_limited_npot] || FeatureAvailable[IRR_GL_OES_texture_npot]; - case EVDF_MIP_MAP_AUTO_UPDATE: - return Version > 100; - case EVDF_BLEND_OPERATIONS: - return FeatureAvailable[IRR_GL_OES_blend_subtract]; - case EVDF_BLEND_SEPARATE: - return FeatureAvailable[IRR_GL_OES_blend_func_separate]; - case EVDF_FRAMEBUFFER_OBJECT: - return FeatureAvailable[IRR_GL_OES_framebuffer_object]; - case EVDF_VERTEX_BUFFER_OBJECT: - return Version > 100; - default: - return true; - }; - } - - inline void irrGlActiveTexture(GLenum texture) - { - glActiveTexture(texture); - } - - inline void irrGlCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, - GLsizei imageSize, const void *data) - { - glCompressedTexImage2D(target, level, internalformat, width, height, border, imageSize, data); - } - - inline void irrGlCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, - GLenum format, GLsizei imageSize, const void *data) - { - glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data); - } - - inline void irrGlUseProgram(GLuint prog) - { - } - - inline void irrGlBindFramebuffer(GLenum target, GLuint framebuffer) - { - if (pGlBindFramebufferOES) - pGlBindFramebufferOES(target, framebuffer); - } - - inline void irrGlDeleteFramebuffers(GLsizei n, const GLuint *framebuffers) - { - if (pGlDeleteFramebuffersOES) - pGlDeleteFramebuffersOES(n, framebuffers); - } - - inline void irrGlGenFramebuffers(GLsizei n, GLuint *framebuffers) - { - if (pGlGenFramebuffersOES) - pGlGenFramebuffersOES(n, framebuffers); - } - - inline GLenum irrGlCheckFramebufferStatus(GLenum target) - { - if (pGlCheckFramebufferStatusOES) - return pGlCheckFramebufferStatusOES(target); - else - return 0; - } - - inline void irrGlFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) - { - if (pGlFramebufferTexture2DOES) - pGlFramebufferTexture2DOES(target, attachment, textarget, texture, level); - } - - inline void irrGlGenerateMipmap(GLenum target) - { - if (pGlGenerateMipmapOES) - pGlGenerateMipmapOES(target); - } - - inline void irrGlActiveStencilFace(GLenum face) - { - } - - inline void irrGlDrawBuffer(GLenum mode) - { - } - - inline void irrGlDrawBuffers(GLsizei n, const GLenum *bufs) - { - } - - inline void irrGlBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) - { - if (pGlBlendFuncSeparateOES) - pGlBlendFuncSeparateOES(srcRGB, dstRGB, srcAlpha, dstAlpha); - } - - inline void irrGlBlendEquation(GLenum mode) - { - if (pGlBlendEquationOES) - pGlBlendEquationOES(mode); - } - - inline void irrGlEnableIndexed(GLenum target, GLuint index) - { - } - - inline void irrGlDisableIndexed(GLenum target, GLuint index) - { - } - - inline void irrGlColorMaskIndexed(GLuint buf, GLboolean r, GLboolean g, GLboolean b, GLboolean a) - { - } - - inline void irrGlBlendFuncIndexed(GLuint buf, GLenum src, GLenum dst) - { - } - - inline void irrGlBlendFuncSeparateIndexed(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) - { - } - - inline void irrGlBlendEquationIndexed(GLuint buf, GLenum mode) - { - } - - inline void irrGlBlendEquationSeparateIndexed(GLuint buf, GLenum modeRGB, GLenum modeAlpha) - { - } - -protected: - u8 MaxLights; - - PFNGLBLENDEQUATIONOESPROC pGlBlendEquationOES; - PFNGLBLENDFUNCSEPARATEOESPROC pGlBlendFuncSeparateOES; - PFNGLBINDFRAMEBUFFEROESPROC pGlBindFramebufferOES; - PFNGLDELETEFRAMEBUFFERSOESPROC pGlDeleteFramebuffersOES; - PFNGLGENFRAMEBUFFERSOESPROC pGlGenFramebuffersOES; - PFNGLCHECKFRAMEBUFFERSTATUSOESPROC pGlCheckFramebufferStatusOES; - PFNGLFRAMEBUFFERTEXTURE2DOESPROC pGlFramebufferTexture2DOES; - PFNGLGENERATEMIPMAPOESPROC pGlGenerateMipmapOES; -}; - -} -} - -#endif diff --git a/irr/src/COGLESMaterialRenderer.h b/irr/src/COGLESMaterialRenderer.h deleted file mode 100644 index 69d1b81ff..000000000 --- a/irr/src/COGLESMaterialRenderer.h +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright (C) 2002-2008 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#pragma once - -#ifdef _IRR_COMPILE_WITH_OGLES1_ - -#include "COGLESDriver.h" -#include "IMaterialRenderer.h" - -namespace irr -{ -namespace video -{ - -//! Base class for all internal OGLES1 material renderers -class COGLES1MaterialRenderer : public IMaterialRenderer -{ -public: - //! Constructor - COGLES1MaterialRenderer(video::COGLES1Driver *driver) : - Driver(driver) - { - } - -protected: - video::COGLES1Driver *Driver; -}; - -//! Solid material renderer -class COGLES1MaterialRenderer_SOLID : public COGLES1MaterialRenderer -{ -public: - COGLES1MaterialRenderer_SOLID(video::COGLES1Driver *d) : - COGLES1MaterialRenderer(d) {} - - virtual void OnSetMaterial(const SMaterial &material, const SMaterial &lastMaterial, - bool resetAllRenderstates, IMaterialRendererServices *services) - { - Driver->setBasicRenderStates(material, lastMaterial, resetAllRenderstates); - - if (resetAllRenderstates || (material.MaterialType != lastMaterial.MaterialType)) { - // thanks to Murphy, the following line removed some - // bugs with several OGLES1 implementations. - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - } - } -}; - -//! Generic Texture Blend -class COGLES1MaterialRenderer_ONETEXTURE_BLEND : public COGLES1MaterialRenderer -{ -public: - COGLES1MaterialRenderer_ONETEXTURE_BLEND(video::COGLES1Driver *d) : - COGLES1MaterialRenderer(d) {} - - virtual void OnSetMaterial(const SMaterial &material, const SMaterial &lastMaterial, - bool resetAllRenderstates, IMaterialRendererServices *services) - { - Driver->setBasicRenderStates(material, lastMaterial, resetAllRenderstates); - - // if (material.MaterialType != lastMaterial.MaterialType || - // material.MaterialTypeParam != lastMaterial.MaterialTypeParam || - // resetAllRenderstates) - { - E_BLEND_FACTOR srcRGBFact, dstRGBFact, srcAlphaFact, dstAlphaFact; - E_MODULATE_FUNC modulate; - u32 alphaSource; - unpack_textureBlendFuncSeparate(srcRGBFact, dstRGBFact, srcAlphaFact, dstAlphaFact, modulate, alphaSource, material.MaterialTypeParam); - - Driver->getCacheHandler()->setBlend(true); - - if (Driver->queryFeature(EVDF_BLEND_SEPARATE)) { - Driver->getCacheHandler()->setBlendFuncSeparate(Driver->getGLBlend(srcRGBFact), Driver->getGLBlend(dstRGBFact), - Driver->getGLBlend(srcAlphaFact), Driver->getGLBlend(dstAlphaFact)); - } else { - Driver->getCacheHandler()->setBlendFunc(Driver->getGLBlend(srcRGBFact), Driver->getGLBlend(dstRGBFact)); - } - - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS); - - glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE, (f32)modulate); - - glEnable(GL_ALPHA_TEST); - glAlphaFunc(GL_GREATER, 0.f); - - if (textureBlendFunc_hasAlpha(srcRGBFact) || textureBlendFunc_hasAlpha(dstRGBFact) || - textureBlendFunc_hasAlpha(srcAlphaFact) || textureBlendFunc_hasAlpha(dstAlphaFact)) { - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE); - - glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR); - } - } - } - - virtual void OnUnsetMaterial() - { - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE, 1.f); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS); - - Driver->getCacheHandler()->setBlend(false); - glDisable(GL_ALPHA_TEST); - } - - //! Returns if the material is transparent. - /** Is not always transparent, but mostly. */ - virtual bool isTransparent() const - { - return true; - } - -private: - u32 getGLBlend(E_BLEND_FACTOR factor) const - { - u32 r = 0; - switch (factor) { - case EBF_ZERO: - r = GL_ZERO; - break; - case EBF_ONE: - r = GL_ONE; - break; - case EBF_DST_COLOR: - r = GL_DST_COLOR; - break; - case EBF_ONE_MINUS_DST_COLOR: - r = GL_ONE_MINUS_DST_COLOR; - break; - case EBF_SRC_COLOR: - r = GL_SRC_COLOR; - break; - case EBF_ONE_MINUS_SRC_COLOR: - r = GL_ONE_MINUS_SRC_COLOR; - break; - case EBF_SRC_ALPHA: - r = GL_SRC_ALPHA; - break; - case EBF_ONE_MINUS_SRC_ALPHA: - r = GL_ONE_MINUS_SRC_ALPHA; - break; - case EBF_DST_ALPHA: - r = GL_DST_ALPHA; - break; - case EBF_ONE_MINUS_DST_ALPHA: - r = GL_ONE_MINUS_DST_ALPHA; - break; - case EBF_SRC_ALPHA_SATURATE: - r = GL_SRC_ALPHA_SATURATE; - break; - } - return r; - } -}; - -//! Transparent vertex alpha material renderer -class COGLES1MaterialRenderer_TRANSPARENT_VERTEX_ALPHA : public COGLES1MaterialRenderer -{ -public: - COGLES1MaterialRenderer_TRANSPARENT_VERTEX_ALPHA(video::COGLES1Driver *d) : - COGLES1MaterialRenderer(d) {} - - virtual void OnSetMaterial(const SMaterial &material, const SMaterial &lastMaterial, - bool resetAllRenderstates, IMaterialRendererServices *services) - { - Driver->setBasicRenderStates(material, lastMaterial, resetAllRenderstates); - - Driver->getCacheHandler()->setBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - Driver->getCacheHandler()->setBlend(true); - - if (material.MaterialType != lastMaterial.MaterialType || resetAllRenderstates) { - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PRIMARY_COLOR); - - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PRIMARY_COLOR); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE); - } - } - - virtual void OnUnsetMaterial() - { - // default values - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PREVIOUS); - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS); - - Driver->getCacheHandler()->setBlend(false); - } - - //! Returns if the material is transparent. - virtual bool isTransparent() const - { - return true; - } -}; - -//! Transparent alpha channel material renderer -class COGLES1MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL : public COGLES1MaterialRenderer -{ -public: - COGLES1MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL(video::COGLES1Driver *d) : - COGLES1MaterialRenderer(d) {} - - virtual void OnSetMaterial(const SMaterial &material, const SMaterial &lastMaterial, - bool resetAllRenderstates, IMaterialRendererServices *services) - { - Driver->setBasicRenderStates(material, lastMaterial, resetAllRenderstates); - - Driver->getCacheHandler()->setBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - Driver->getCacheHandler()->setBlend(true); - - if (material.MaterialType != lastMaterial.MaterialType || resetAllRenderstates || material.MaterialTypeParam != lastMaterial.MaterialTypeParam) { - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS); - - glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); - glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE); - - glEnable(GL_ALPHA_TEST); - - glAlphaFunc(GL_GREATER, material.MaterialTypeParam); - } - } - - virtual void OnUnsetMaterial() - { - glDisable(GL_ALPHA_TEST); - Driver->getCacheHandler()->setBlend(false); - } - - //! Returns if the material is transparent. - virtual bool isTransparent() const - { - return true; - } -}; - -//! Transparent alpha channel material renderer -class COGLES1MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL_REF : public COGLES1MaterialRenderer -{ -public: - COGLES1MaterialRenderer_TRANSPARENT_ALPHA_CHANNEL_REF(video::COGLES1Driver *d) : - COGLES1MaterialRenderer(d) {} - - virtual void OnSetMaterial(const SMaterial &material, const SMaterial &lastMaterial, - bool resetAllRenderstates, IMaterialRendererServices *services) - { - Driver->setBasicRenderStates(material, lastMaterial, resetAllRenderstates); - - if (material.MaterialType != lastMaterial.MaterialType || resetAllRenderstates) { - glEnable(GL_ALPHA_TEST); - glAlphaFunc(GL_GREATER, 0.5f); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - } - } - - virtual void OnUnsetMaterial() - { - glDisable(GL_ALPHA_TEST); - } - - //! Returns if the material is transparent. - virtual bool isTransparent() const - { - return false; // this material is not really transparent because it does no blending. - } -}; - -} // end namespace video -} // end namespace irr - -#endif diff --git a/irr/src/COpenGLCoreCacheHandler.h b/irr/src/COpenGLCoreCacheHandler.h index ae3661313..a1277bfed 100644 --- a/irr/src/COpenGLCoreCacheHandler.h +++ b/irr/src/COpenGLCoreCacheHandler.h @@ -84,12 +84,12 @@ class COpenGLCoreCacheHandler if (curTextureType != prevTextureType) { GL.BindTexture(prevTextureType, 0); -#if (defined(IRR_COMPILE_GL_COMMON) || defined(IRR_COMPILE_GLES_COMMON)) +#if defined(IRR_COMPILE_GL_COMMON) GL.Disable(prevTextureType); GL.Enable(curTextureType); #endif } -#if (defined(IRR_COMPILE_GL_COMMON) || defined(IRR_COMPILE_GLES_COMMON)) +#if defined(IRR_COMPILE_GL_COMMON) else if (!prevTexture) GL.Enable(curTextureType); #endif @@ -109,7 +109,7 @@ class COpenGLCoreCacheHandler GL.BindTexture(prevTextureType, 0); -#if (defined(IRR_COMPILE_GL_COMMON) || defined(IRR_COMPILE_GLES_COMMON)) +#if defined(IRR_COMPILE_GL_COMMON) GL.Disable(prevTextureType); #endif } @@ -222,7 +222,7 @@ public: Driver->irrGlActiveTexture(ActiveTexture); -#if (defined(IRR_COMPILE_GL_COMMON) || defined(IRR_COMPILE_GLES_COMMON)) +#if defined(IRR_COMPILE_GL_COMMON) GL.Disable(GL_TEXTURE_2D); #endif diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 218eabece..95cac4c22 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -296,8 +296,7 @@ public: delete[] tmpBuffer; } -#elif (defined(IRR_COMPILE_GLES2_COMMON) || defined(IRR_COMPILE_GLES_COMMON)) - // TODO: on ES2 we can likely also work with glCopyTexImage2D instead of rendering which should be faster. +#elif defined(IRR_COMPILE_GLES2_COMMON) COpenGLCoreTexture *tmpTexture = new COpenGLCoreTexture("OGL_CORE_LOCK_TEXTURE", Size, ETT_2D, ColorFormat, Driver); GLuint tmpFBO = 0; diff --git a/irr/src/Irrlicht.cpp b/irr/src/Irrlicht.cpp index d119584e3..eb94fa4eb 100644 --- a/irr/src/Irrlicht.cpp +++ b/irr/src/Irrlicht.cpp @@ -100,10 +100,6 @@ extern "C" IRRLICHT_API bool IRRCALLCONV isDriverSupported(E_DRIVER_TYPE driver) case EDT_OPENGL: return true; #endif -#ifdef _IRR_COMPILE_WITH_OGLES1_ - case EDT_OGLES1: - return true; -#endif #ifdef _IRR_COMPILE_WITH_OGLES2_ case EDT_OGLES2: return true; diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 18b9ff158..4400dd90e 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -418,7 +418,6 @@ std::vector RenderingEngine::getSupportedVideoDrivers() video::EDT_OPENGL, video::EDT_OPENGL3, video::EDT_OGLES2, - video::EDT_OGLES1, video::EDT_NULL, }; std::vector drivers; @@ -454,7 +453,6 @@ const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_ {(int)video::EDT_NULL, {"null", "NULL Driver"}}, {(int)video::EDT_OPENGL, {"opengl", "OpenGL"}}, {(int)video::EDT_OPENGL3, {"opengl3", "OpenGL 3+"}}, - {(int)video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}}, {(int)video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}}, }; return driver_info_map.at((int)type); From 1977517d7ab9aaad1e7991c030c412480eb3a103 Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Wed, 15 May 2024 21:11:16 +0200 Subject: [PATCH 043/200] Rename TouchScreenGUI -> TouchControls to avoid confusion between touchscreen-related settings that affect GUIs (formspecs) and touchscreen-related settings that affect the touch controls (TouchControls / formerly TouchScreenGUI) --- src/client/clientlauncher.cpp | 8 +-- src/client/game.cpp | 54 +++++++++---------- src/client/hud.cpp | 10 ++-- src/client/inputhandler.cpp | 20 +++---- src/clientdynamicinfo.cpp | 4 +- src/gui/CMakeLists.txt | 2 +- src/gui/guiKeyChangeMenu.cpp | 2 +- src/gui/modalMenu.cpp | 6 +-- .../{touchscreengui.cpp => touchcontrols.cpp} | 52 +++++++++--------- src/gui/{touchscreengui.h => touchcontrols.h} | 8 +-- 10 files changed, 83 insertions(+), 83 deletions(-) rename src/gui/{touchscreengui.cpp => touchcontrols.cpp} (94%) rename src/gui/{touchscreengui.h => touchcontrols.h} (97%) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 5365d70f9..5826508f6 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/mainmenumanager.h" #include "clouds.h" -#include "gui/touchscreengui.h" +#include "gui/touchcontrols.h" #include "server.h" #include "filesys.h" #include "gui/guiMainMenu.h" @@ -230,9 +230,9 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) m_rendering_engine->get_scene_manager()->clear(); - if (g_touchscreengui) { - delete g_touchscreengui; - g_touchscreengui = NULL; + if (g_touchcontrols) { + delete g_touchcontrols; + g_touchcontrols = NULL; } /* Save the settings when leaving the game. diff --git a/src/client/game.cpp b/src/client/game.cpp index 1267b32a9..648155d83 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -40,7 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content/subgames.h" #include "client/event_manager.h" #include "fontengine.h" -#include "gui/touchscreengui.h" +#include "gui/touchcontrols.h" #include "itemdef.h" #include "log.h" #include "filesys.h" @@ -1245,8 +1245,8 @@ void Game::shutdown() // Clear text when exiting. m_game_ui->clearText(); - if (g_touchscreengui) - g_touchscreengui->hide(); + if (g_touchcontrols) + g_touchcontrols->hide(); // only if the shutdown progress bar isn't shown yet if (m_shutdown_progress == 0.0f) @@ -1510,8 +1510,8 @@ bool Game::createClient(const GameStartData &start_data) client->getScript()->on_camera_ready(camera); client->setCamera(camera); - if (g_touchscreengui) { - g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled()); + if (g_touchcontrols) { + g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); } /* Clouds @@ -1579,7 +1579,7 @@ bool Game::initGui() -1, chat_backend, client, &g_menumgr); if (g_settings->getBool("enable_touch")) - g_touchscreengui = new TouchScreenGUI(device, texture_src); + g_touchcontrols = new TouchControls(device, texture_src); return true; } @@ -2027,15 +2027,15 @@ void Game::processUserInput(f32 dtime) input->clear(); } - if (g_touchscreengui) - g_touchscreengui->hide(); + if (g_touchcontrols) + g_touchcontrols->hide(); } else { - if (g_touchscreengui) { - /* on touchscreengui step may generate own input events which ain't + if (g_touchcontrols) { + /* on touchcontrols step may generate own input events which ain't * what we want in case we just did clear them */ - g_touchscreengui->show(); - g_touchscreengui->step(dtime); + g_touchcontrols->show(); + g_touchcontrols->step(dtime); } m_game_focused = true; @@ -2230,8 +2230,8 @@ void Game::processItemSelection(u16 *new_playeritem) } } - if (g_touchscreengui) { - std::optional selection = g_touchscreengui->getHotbarSelection(); + if (g_touchcontrols) { + std::optional selection = g_touchcontrols->getHotbarSelection(); if (selection) *new_playeritem = *selection; } @@ -2636,7 +2636,7 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime) this results in duplicated input. To avoid that, we don't enable relative mouse mode if we're in touchscreen mode. */ if (cur_control) - cur_control->setRelativeMode(!g_touchscreengui && !isMenuActive()); + cur_control->setRelativeMode(!g_touchcontrols && !isMenuActive()); if ((device->isWindowActive() && device->isWindowFocused() && !isMenuActive()) || input->isRandom()) { @@ -2679,9 +2679,9 @@ f32 Game::getSensitivityScaleFactor() const void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) { - if (g_touchscreengui) { - cam->camera_yaw += g_touchscreengui->getYawChange(); - cam->camera_pitch += g_touchscreengui->getPitchChange(); + if (g_touchcontrols) { + cam->camera_yaw += g_touchcontrols->getYawChange(); + cam->camera_pitch += g_touchcontrols->getPitchChange(); } else { v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2); v2s32 dist = input->getMousePos() - center; @@ -2746,7 +2746,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam) * touch then its meaning is inverted (i.e. holding aux1 means walk and * not fast) */ - if (g_touchscreengui && m_touch_simulate_aux1) { + if (g_touchcontrols && m_touch_simulate_aux1) { control.aux1 = control.aux1 ^ true; } @@ -3235,8 +3235,8 @@ void Game::updateCamera(f32 dtime) camera->toggleCameraMode(); - if (g_touchscreengui) - g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled()); + if (g_touchcontrols) + g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); // Make the player visible depending on camera mode. playercao->updateMeshCulling(); @@ -3337,8 +3337,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) } shootline.end = shootline.start + camera_direction * BS * d; - if (g_touchscreengui && isTouchCrosshairDisabled()) { - shootline = g_touchscreengui->getShootline(); + if (g_touchcontrols && isTouchCrosshairDisabled()) { + shootline = g_touchcontrols->getShootline(); // Scale shootline to the acual distance the player can reach shootline.end = shootline.start + shootline.getVector().normalize() * BS * d; @@ -3355,9 +3355,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) if (pointed != runData.pointed_old) infostream << "Pointing at " << pointed.dump() << std::endl; - if (g_touchscreengui) { + if (g_touchcontrols) { auto mode = selected_def.touch_interaction.getMode(pointed.type); - g_touchscreengui->applyContextControls(mode); + g_touchcontrols->applyContextControls(mode); } // Note that updating the selection mesh every frame is not particularly efficient, @@ -4348,7 +4348,7 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats) (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && (this->camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); - if (g_touchscreengui && isTouchCrosshairDisabled()) + if (g_touchcontrols && isTouchCrosshairDisabled()) draw_crosshair = false; this->m_rendering_engine->draw_scene(sky_color, this->m_game_ui->m_flags.show_hud, @@ -4459,7 +4459,7 @@ void Game::showPauseMenu() { std::string control_text; - if (g_touchscreengui) { + if (g_touchcontrols) { control_text = strgettext("Controls:\n" "No menu open:\n" "- slide finger: look around\n" diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 3f9672f3d..e2d1ee455 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -39,7 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "wieldmesh.h" #include "client/renderingengine.h" #include "client/minimap.h" -#include "gui/touchscreengui.h" +#include "gui/touchcontrols.h" #include "util/enriched_string.h" #include "irrlicht_changes/CGUITTFont.h" @@ -322,8 +322,8 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, drawItem(mainlist->getItem(i), item_rect, (i + 1) == selectitem); - if (is_hotbar && g_touchscreengui) - g_touchscreengui->registerHotbarRect(i, item_rect); + if (is_hotbar && g_touchcontrols) + g_touchcontrols->registerHotbarRect(i, item_rect); } } @@ -787,8 +787,8 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, void Hud::drawHotbar(u16 playeritem) { - if (g_touchscreengui) - g_touchscreengui->resetHotbarRects(); + if (g_touchcontrols) + g_touchcontrols->resetHotbarRects(); InventoryList *mainlist = inventory->getList("main"); if (mainlist == NULL) { diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 4233916c2..39c212d2f 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "inputhandler.h" #include "gui/mainmenumanager.h" -#include "gui/touchscreengui.h" +#include "gui/touchcontrols.h" #include "hud.h" void KeyCache::populate_nonchanging() @@ -143,8 +143,8 @@ bool MyEventReceiver::OnEvent(const SEvent &event) // Let the menu handle events, if one is active. if (isMenuActive()) { - if (g_touchscreengui) - g_touchscreengui->setVisible(false); + if (g_touchcontrols) + g_touchcontrols->setVisible(false); return g_menumgr.preprocessEvent(event); } @@ -168,9 +168,9 @@ bool MyEventReceiver::OnEvent(const SEvent &event) return true; } - } else if (g_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) { - // In case of touchscreengui, we have to handle different events - g_touchscreengui->translateEvent(event); + } else if (g_touchcontrols && event.EventType == irr::EET_TOUCH_INPUT_EVENT) { + // In case of touchcontrols, we have to handle different events + g_touchcontrols->translateEvent(event); return true; } else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { // joystick may be nullptr if game is launched with '--random-input' parameter @@ -237,8 +237,8 @@ float RealInputHandler::getMovementSpeed() return 0.0f; return 1.0f; // If there is a keyboard event, assume maximum speed } - if (g_touchscreengui && g_touchscreengui->getMovementSpeed()) - return g_touchscreengui->getMovementSpeed(); + if (g_touchcontrols && g_touchcontrols->getMovementSpeed()) + return g_touchcontrols->getMovementSpeed(); return joystick.getMovementSpeed(); } @@ -260,8 +260,8 @@ float RealInputHandler::getMovementDirection() return std::atan2(x, z); // `getMovementDirection() == 0` means forward, so we cannot use // `getMovementDirection()` as a condition. - else if (g_touchscreengui && g_touchscreengui->getMovementSpeed()) - return g_touchscreengui->getMovementDirection(); + else if (g_touchcontrols && g_touchcontrols->getMovementSpeed()) + return g_touchcontrols->getMovementDirection(); return joystick.getMovementDirection(); } diff --git a/src/clientdynamicinfo.cpp b/src/clientdynamicinfo.cpp index a9b2a6ef3..2e75a6adc 100644 --- a/src/clientdynamicinfo.cpp +++ b/src/clientdynamicinfo.cpp @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "client/renderingengine.h" -#include "gui/touchscreengui.h" +#include "gui/touchcontrols.h" ClientDynamicInfo ClientDynamicInfo::getCurrent() { @@ -33,7 +33,7 @@ ClientDynamicInfo ClientDynamicInfo::getCurrent() f32 hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f); f32 real_gui_scaling = gui_scaling * density; f32 real_hud_scaling = hud_scaling * density; - bool touch_controls = g_touchscreengui; + bool touch_controls = g_touchcontrols; return { screen_size, real_gui_scaling, real_hud_scaling, diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 66d6a9ee8..73bbecb02 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -25,6 +25,6 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp ${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/profilergraph.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/touchscreengui.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/touchcontrols.cpp PARENT_SCOPE ) diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index 7e6c486e0..97e1ae8e0 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -377,7 +377,7 @@ void GUIKeyChangeMenu::add_key(int id, std::wstring button_name, const std::stri key_settings.push_back(k); } -// compare with button_titles in touchscreengui.cpp +// compare with button_titles in touchcontrols.cpp void GUIKeyChangeMenu::init_keys() { this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wstrgettext("Forward"), "keymap_forward"); diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 83d5036f9..fd60d08c2 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/guiInventoryList.h" #include "porting.h" #include "settings.h" -#include "touchscreengui.h" +#include "touchcontrols.h" PointerAction PointerAction::fromEvent(const SEvent &event) { switch (event.EventType) { @@ -111,8 +111,8 @@ void GUIModalMenu::quitMenu() Environment->removeFocus(this); m_menumgr->deletingMenu(this); this->remove(); - if (g_touchscreengui) - g_touchscreengui->show(); + if (g_touchcontrols) + g_touchcontrols->show(); } static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent) diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchcontrols.cpp similarity index 94% rename from src/gui/touchscreengui.cpp rename to src/gui/touchcontrols.cpp index c428ef52e..b96ced1bf 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchcontrols.cpp @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "touchscreengui.h" +#include "touchcontrols.h" #include "gettime.h" #include "irr_v2d.h" @@ -39,7 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -TouchScreenGUI *g_touchscreengui; +TouchControls *g_touchcontrols; static const char *button_image_names[] = { "jump_btn.png", @@ -266,14 +266,14 @@ static EKEY_CODE id_to_keycode(touch_gui_button_id id) code = keyname_to_keycode(resolved.c_str()); } catch (UnknownKeycode &e) { code = KEY_UNKNOWN; - warningstream << "TouchScreenGUI: Unknown key '" << resolved + warningstream << "TouchControls: Unknown key '" << resolved << "' for '" << key << "', hiding button." << std::endl; } return code; } -TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, ISimpleTextureSource *tsrc): +TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc): m_device(device), m_guienv(device->getGUIEnvironment()), m_receiver(device->getEventReceiver()), @@ -413,7 +413,7 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, ISimpleTextureSource *tsr } } -void TouchScreenGUI::addButton(std::vector &buttons, touch_gui_button_id id, +void TouchControls::addButton(std::vector &buttons, touch_gui_button_id id, const std::string &image, const recti &rect, bool visible) { IGUIImage *btn_gui_button = m_guienv->addImage(rect, nullptr, id); @@ -426,7 +426,7 @@ void TouchScreenGUI::addButton(std::vector &buttons, touch_gui_butt btn.gui_button = grab_gui_element(btn_gui_button); } -void TouchScreenGUI::addToggleButton(std::vector &buttons, touch_gui_button_id id, +void TouchControls::addToggleButton(std::vector &buttons, touch_gui_button_id id, const std::string &image_1, const std::string &image_2, const recti &rect, bool visible) { addButton(buttons, id, image_1, rect, visible); @@ -436,7 +436,7 @@ void TouchScreenGUI::addToggleButton(std::vector &buttons, touch_gu btn.toggle_textures[1] = image_2; } -IGUIImage *TouchScreenGUI::makeButtonDirect(touch_gui_button_id id, +IGUIImage *TouchControls::makeButtonDirect(touch_gui_button_id id, const recti &rect, bool visible) { IGUIImage *btn_gui_button = m_guienv->addImage(rect, nullptr, id); @@ -447,7 +447,7 @@ IGUIImage *TouchScreenGUI::makeButtonDirect(touch_gui_button_id id, return btn_gui_button; } -bool TouchScreenGUI::isHotbarButton(const SEvent &event) +bool TouchControls::isHotbarButton(const SEvent &event) { const v2s32 touch_pos = v2s32(event.TouchInput.X, event.TouchInput.Y); // check if hotbar item is pressed @@ -462,14 +462,14 @@ bool TouchScreenGUI::isHotbarButton(const SEvent &event) return false; } -std::optional TouchScreenGUI::getHotbarSelection() +std::optional TouchControls::getHotbarSelection() { auto selection = m_hotbar_selection; m_hotbar_selection = std::nullopt; return selection; } -void TouchScreenGUI::handleReleaseEvent(size_t pointer_id) +void TouchControls::handleReleaseEvent(size_t pointer_id) { // By the way: Android reuses pointer IDs, so m_pointer_pos[pointer_id] // will be overwritten soon anyway. @@ -516,15 +516,15 @@ void TouchScreenGUI::handleReleaseEvent(size_t pointer_id) m_joystick_btn_bg->setVisible(false); m_joystick_btn_center->setVisible(false); } else { - infostream << "TouchScreenGUI::translateEvent released unknown button: " + infostream << "TouchControls::translateEvent released unknown button: " << pointer_id << std::endl; } } -void TouchScreenGUI::translateEvent(const SEvent &event) +void TouchControls::translateEvent(const SEvent &event) { if (!m_visible) { - infostream << "TouchScreenGUI::translateEvent got event but is not visible!" + infostream << "TouchControls::translateEvent got event but is not visible!" << std::endl; return; } @@ -702,7 +702,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) } } -void TouchScreenGUI::applyJoystickStatus() +void TouchControls::applyJoystickStatus() { if (m_joystick_triggers_aux1) { SEvent translated{}; @@ -718,7 +718,7 @@ void TouchScreenGUI::applyJoystickStatus() } } -void TouchScreenGUI::step(float dtime) +void TouchControls::step(float dtime) { if (m_overflow_open) { buttons_step(m_overflow_buttons, dtime, m_device->getVideoDriver(), m_receiver, m_texturesource); @@ -757,17 +757,17 @@ void TouchScreenGUI::step(float dtime) m_had_move_id = false; } -void TouchScreenGUI::resetHotbarRects() +void TouchControls::resetHotbarRects() { m_hotbar_rects.clear(); } -void TouchScreenGUI::registerHotbarRect(u16 index, const recti &rect) +void TouchControls::registerHotbarRect(u16 index, const recti &rect) { m_hotbar_rects[index] = rect; } -void TouchScreenGUI::setVisible(bool visible) +void TouchControls::setVisible(bool visible) { if (m_visible == visible) return; @@ -781,14 +781,14 @@ void TouchScreenGUI::setVisible(bool visible) updateVisibility(); } -void TouchScreenGUI::toggleOverflowMenu() +void TouchControls::toggleOverflowMenu() { releaseAll(); // must be done first m_overflow_open = !m_overflow_open; updateVisibility(); } -void TouchScreenGUI::updateVisibility() +void TouchControls::updateVisibility() { bool regular_visible = m_visible && !m_overflow_open; for (auto &button : m_buttons) @@ -804,7 +804,7 @@ void TouchScreenGUI::updateVisibility() text->setVisible(overflow_visible); } -void TouchScreenGUI::releaseAll() +void TouchControls::releaseAll() { while (!m_pointer_pos.empty()) handleReleaseEvent(m_pointer_pos.begin()->first); @@ -821,17 +821,17 @@ void TouchScreenGUI::releaseAll() } } -void TouchScreenGUI::hide() +void TouchControls::hide() { setVisible(false); } -void TouchScreenGUI::show() +void TouchControls::show() { setVisible(true); } -v2s32 TouchScreenGUI::getPointerPos() +v2s32 TouchControls::getPointerPos() { if (m_draw_crosshair) return v2s32(m_screensize.X / 2, m_screensize.Y / 2); @@ -840,7 +840,7 @@ v2s32 TouchScreenGUI::getPointerPos() return m_move_pos; } -void TouchScreenGUI::emitMouseEvent(EMOUSE_INPUT_EVENT type) +void TouchControls::emitMouseEvent(EMOUSE_INPUT_EVENT type) { v2s32 pointer_pos = getPointerPos(); @@ -855,7 +855,7 @@ void TouchScreenGUI::emitMouseEvent(EMOUSE_INPUT_EVENT type) m_receiver->OnEvent(event); } -void TouchScreenGUI::applyContextControls(const TouchInteractionMode &mode) +void TouchControls::applyContextControls(const TouchInteractionMode &mode) { // Since the pointed thing has already been determined when this function // is called, we cannot use this function to update the shootline. diff --git a/src/gui/touchscreengui.h b/src/gui/touchcontrols.h similarity index 97% rename from src/gui/touchscreengui.h rename to src/gui/touchcontrols.h index 2da9d8151..ebc251bb6 100644 --- a/src/gui/touchscreengui.h +++ b/src/gui/touchcontrols.h @@ -128,10 +128,10 @@ struct button_info }; -class TouchScreenGUI +class TouchControls { public: - TouchScreenGUI(IrrlichtDevice *device, ISimpleTextureSource *tsrc); + TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc); void translateEvent(const SEvent &event); void applyContextControls(const TouchInteractionMode &mode); @@ -182,7 +182,7 @@ private: s32 m_button_size; double m_touchscreen_threshold; u16 m_long_tap_delay; - bool m_visible = true; // is the whole touch screen gui visible + bool m_visible = true; std::unordered_map m_hotbar_rects; std::optional m_hotbar_selection = std::nullopt; @@ -273,4 +273,4 @@ private: u64 m_place_pressed_until = 0; }; -extern TouchScreenGUI *g_touchscreengui; +extern TouchControls *g_touchcontrols; From 3a59fabefe421ffdab9c61b9abfe151bc95e5435 Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Sun, 2 Jun 2024 12:58:41 -0700 Subject: [PATCH 044/200] split enable_touch to touch_controls (for touchscreen controls) and touch_gui touch_gui provide adjustment to the interface, so it's more touch friendly Signed-off-by: David Heidelberg --- builtin/fstk/buttonbar.lua | 2 +- builtin/mainmenu/content/dlg_contentdb.lua | 6 ++-- builtin/mainmenu/content/dlg_install.lua | 14 ++++---- builtin/mainmenu/settings/dlg_settings.lua | 24 +++++++------- builtin/mainmenu/tab_local.lua | 2 +- builtin/settingtypes.txt | 37 ++++++++++++---------- src/client/game.cpp | 2 +- src/clientdynamicinfo.cpp | 2 +- src/defaultsettings.cpp | 3 +- src/gui/guiFormSpecMenu.cpp | 4 +-- src/migratesettings.h | 8 +++++ 11 files changed, 57 insertions(+), 47 deletions(-) diff --git a/builtin/fstk/buttonbar.lua b/builtin/fstk/buttonbar.lua index 59b7a586f..64ac37f03 100644 --- a/builtin/fstk/buttonbar.lua +++ b/builtin/fstk/buttonbar.lua @@ -19,7 +19,7 @@ local BASE_SPACING = 0.1 local function get_scroll_btn_width() - return core.settings:get_bool("enable_touch") and 0.8 or 0.5 + return core.settings:get_bool("touch_gui") and 0.8 or 0.5 end local function buttonbar_formspec(self) diff --git a/builtin/mainmenu/content/dlg_contentdb.lua b/builtin/mainmenu/content/dlg_contentdb.lua index 84ef96800..bcc89f7cd 100644 --- a/builtin/mainmenu/content/dlg_contentdb.lua +++ b/builtin/mainmenu/content/dlg_contentdb.lua @@ -181,7 +181,7 @@ local function get_info_formspec(text) return table.concat({ "formspec_version[6]", "size[15.75,9.5]", - core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]", + core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "position[0.5,0.55]", "label[4,4.35;", text, "]", "container[0,", H - 0.8 - 0.375, "]", @@ -212,7 +212,7 @@ local function get_formspec(dlgdata) local formspec = { "formspec_version[6]", "size[15.75,9.5]", - core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]", + core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "position[0.5,0.55]", "style[status,downloading,queued;border=false]", @@ -463,7 +463,7 @@ end local function handle_events(event) if event == "DialogShow" then -- On touchscreen, don't show the "MINETEST" header behind the dialog. - mm_game_theme.set_engine(core.settings:get_bool("enable_touch")) + mm_game_theme.set_engine(core.settings:get_bool("touch_gui")) -- If ContentDB is already loaded, auto-install packages here. do_auto_install() diff --git a/builtin/mainmenu/content/dlg_install.lua b/builtin/mainmenu/content/dlg_install.lua index 0549e23be..89819be2a 100644 --- a/builtin/mainmenu/content/dlg_install.lua +++ b/builtin/mainmenu/content/dlg_install.lua @@ -22,13 +22,13 @@ end local function get_loading_formspec() - local ENABLE_TOUCH = core.settings:get_bool("enable_touch") - local w = ENABLE_TOUCH and 14 or 7 + local TOUCH_GUI = core.settings:get_bool("touch_gui") + local w = TOUCH_GUI and 14 or 7 local formspec = { "formspec_version[3]", "size[", w, ",9.05]", - ENABLE_TOUCH and "padding[0.01,0.01]" or "position[0.5,0.55]", + TOUCH_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]", "label[3,4.525;", fgettext("Loading..."), "]", } return table.concat(formspec) @@ -110,18 +110,18 @@ local function get_formspec(data) message_bg = mt_color_orange end - local ENABLE_TOUCH = core.settings:get_bool("enable_touch") + local TOUCH_GUI = core.settings:get_bool("touch_gui") - local w = ENABLE_TOUCH and 14 or 7 + local w = TOUCH_GUI and 14 or 7 local padded_w = w - 2*0.375 - local dropdown_w = ENABLE_TOUCH and 10.2 or 4.25 + local dropdown_w = TOUCH_GUI and 10.2 or 4.25 local button_w = (padded_w - 0.25) / 3 local button_pad = button_w / 2 local formspec = { "formspec_version[3]", "size[", w, ",9.05]", - ENABLE_TOUCH and "padding[0.01,0.01]" or "position[0.5,0.55]", + TOUCH_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]", "style[title;border=false]", "box[0,0;", w, ",0.8;#3333]", "button[0,0;", w, ",0.8;title;", fgettext("Install $1", package.title) , "]", diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 73a72769b..75f99376d 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -110,7 +110,7 @@ local function load() local change_keys = { query_text = "Controls", requires = { - keyboard_mouse = true, + touch_controls = false, }, get_formspec = function(self, avail_w) local btn_w = math.min(avail_w, 3) @@ -324,8 +324,6 @@ local function check_requirements(name, requires) local special = { android = PLATFORM == "Android", desktop = PLATFORM ~= "Android", - touchscreen_gui = core.settings:get_bool("enable_touch"), - keyboard_mouse = not core.settings:get_bool("enable_touch"), shaders_support = shaders_support, shaders = core.settings:get_bool("enable_shaders") and shaders_support, opengl = video_driver == "opengl", @@ -457,13 +455,13 @@ local function get_formspec(dialogdata) local extra_h = 1 -- not included in tabsize.height local tabsize = { - width = core.settings:get_bool("enable_touch") and 16.5 or 15.5, - height = core.settings:get_bool("enable_touch") and (10 - extra_h) or 12, + width = core.settings:get_bool("touch_gui") and 16.5 or 15.5, + height = core.settings:get_bool("touch_gui") and (10 - extra_h) or 12, } - local scrollbar_w = core.settings:get_bool("enable_touch") and 0.6 or 0.4 + local scrollbar_w = core.settings:get_bool("touch_gui") and 0.6 or 0.4 - local left_pane_width = core.settings:get_bool("enable_touch") and 4.5 or 4.25 + local left_pane_width = core.settings:get_bool("touch_gui") and 4.5 or 4.25 local left_pane_padding = 0.25 local search_width = left_pane_width + scrollbar_w - (0.75 * 2) @@ -477,7 +475,7 @@ local function get_formspec(dialogdata) local fs = { "formspec_version[6]", "size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]", - core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "", + core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "", "bgcolor[#0000]", -- HACK: this is needed to allow resubmitting the same formspec @@ -652,15 +650,15 @@ local function buttonhandler(this, fields) write_settings_early() end - -- enable_touch is a checkbox in a setting component. We handle this + -- touch_controls is a checkbox in a setting component. We handle this -- setting differently so we can hide/show pages using the next if-statement - if fields.enable_touch ~= nil then - local value = core.is_yes(fields.enable_touch) - core.settings:set_bool("enable_touch", value) + if fields.touch_controls ~= nil then + local value = core.is_yes(fields.touch_controls) + core.settings:set_bool("touch_controls", value) write_settings_early() end - if fields.show_advanced ~= nil or fields.enable_touch ~= nil then + if fields.show_advanced ~= nil or fields.touch_controls ~= nil then local suggested_page_id = update_filtered_pages(dialogdata.query) dialogdata.components = nil diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index 1ed08d825..7f46be213 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -94,7 +94,7 @@ function singleplayer_refresh_gamebar() local btnbar = buttonbar_create( "game_button_bar", - core.settings:get_bool("enable_touch") and {x = 0, y = 7.25} or {x = 0, y = 7.475}, + core.settings:get_bool("touch_gui") and {x = 0, y = 7.25} or {x = 0, y = 7.475}, {x = 15.5, y = 1.25}, "#000000", game_buttonbar_button_handler) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 001eb7288..b5aa142b8 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -61,7 +61,7 @@ # # # This is a comment # # -# # Requires: shaders, enable_dynamic_shadows, !touchscreen_gui +# # Requires: shaders, enable_dynamic_shadows, !touch_controls # name (Readable name) type type_args # # A requirement can be the name of a boolean setting or an engine-defined value. @@ -72,7 +72,6 @@ # * shaders_support (a video driver that supports shaders, may not be enabled) # * shaders (both enable_shaders and shaders_support) # * desktop / android -# * touchscreen_gui / keyboard_mouse # * opengl / gles # * You can negate any requirement by prepending with ! # @@ -92,7 +91,7 @@ camera_smoothing (Camera smoothing) float 0.0 0.0 0.99 # Smooths rotation of camera when in cinematic mode, 0 to disable. Enter cinematic mode by using the key set in Controls. # -# Requires: keyboard_mouse +# Requires: !touch_controls cinematic_camera_smoothing (Camera smoothing in cinematic mode) float 0.7 0.0 0.99 # If enabled, you can place nodes at the position (feet + eye level) where you stand. @@ -113,7 +112,7 @@ always_fly_fast (Always fly fast) bool true # The time in seconds it takes between repeated node placements when holding # the place button. # -# Requires: keyboard_mouse +# Requires: !touch_controls repeat_place_time (Place repetition interval) float 0.25 0.16 2.0 # The minimum time in seconds it takes between digging nodes when holding @@ -132,62 +131,62 @@ safe_dig_and_place (Safe digging and placing) bool false # Invert vertical mouse movement. # -# Requires: keyboard_mouse +# Requires: !touch_controls invert_mouse (Invert mouse) bool false # Mouse sensitivity multiplier. # -# Requires: keyboard_mouse +# Requires: !touch_controls mouse_sensitivity (Mouse sensitivity) float 0.2 0.001 10.0 # Enable mouse wheel (scroll) for item selection in hotbar. # -# Requires: keyboard_mouse +# Requires: !touch_controls enable_hotbar_mouse_wheel (Hotbar: Enable mouse wheel for selection) bool true # Invert mouse wheel (scroll) direction for item selection in hotbar. # -# Requires: keyboard_mouse +# Requires: !touch_controls invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false [*Touchscreen] -# Enables touchscreen mode, allowing you to play the game with a touchscreen. +# Enables the touchscreen controls, allowing you to play the game with a touchscreen. # # Requires: !android -enable_touch (Enable touchscreen) bool true +touch_controls (Enable touchscreen controls) bool true # Touchscreen sensitivity multiplier. # -# Requires: touchscreen_gui +# Requires: touch_controls touchscreen_sensitivity (Touchscreen sensitivity) float 0.2 0.001 10.0 # The length in pixels after which a touch interaction is considered movement. # -# Requires: touchscreen_gui +# Requires: touch_controls touchscreen_threshold (Movement threshold) int 20 0 100 # The delay in milliseconds after which a touch interaction is considered a long tap. # -# Requires: touchscreen_gui +# Requires: touch_controls touch_long_tap_delay (Threshold for long taps) int 400 100 1000 # Use crosshair to select object instead of whole screen. # If enabled, a crosshair will be shown and will be used for selecting object. # -# Requires: touchscreen_gui +# Requires: touch_controls touch_use_crosshair (Use crosshair for touch screen) bool false # Fixes the position of virtual joystick. # If disabled, virtual joystick will center to first-touch's position. # -# Requires: touchscreen_gui +# Requires: touch_controls fixed_virtual_joystick (Fixed virtual joystick) bool false # Use virtual joystick to trigger "Aux1" button. # If enabled, virtual joystick will also tap "Aux1" button when out of main circle. # -# Requires: touchscreen_gui +# Requires: touch_controls virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false # The gesture for punching players/entities. @@ -200,7 +199,7 @@ virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool fals # Known from the classic Minetest mobile controls. # Combat is more or less impossible. # -# Requires: touchscreen_gui +# Requires: touch_controls touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap @@ -687,6 +686,10 @@ language (Language) enum ,be,bg,ca,cs,da,de,el,en,eo,es,et,eu,fi,fr,gd,gl,hu,i [**GUI] +# When enabled, the GUI is optimized to be more usable on touchscreens. +# Whether this is enabled by default depends on your hardware form-factor. +touch_gui (Optimize GUI for touchscreens) bool false + # Scale GUI by a user specified value. # Use a nearest-neighbor-anti-alias filter to scale the GUI. # This will smooth over some of the rough edges, and blend diff --git a/src/client/game.cpp b/src/client/game.cpp index 648155d83..53871d5ac 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1578,7 +1578,7 @@ bool Game::initGui() gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, chat_backend, client, &g_menumgr); - if (g_settings->getBool("enable_touch")) + if (g_settings->getBool("touch_controls")) g_touchcontrols = new TouchControls(device, texture_src); return true; diff --git a/src/clientdynamicinfo.cpp b/src/clientdynamicinfo.cpp index 2e75a6adc..c206018f3 100644 --- a/src/clientdynamicinfo.cpp +++ b/src/clientdynamicinfo.cpp @@ -44,7 +44,7 @@ ClientDynamicInfo ClientDynamicInfo::getCurrent() v2f32 ClientDynamicInfo::calculateMaxFSSize(v2u32 render_target_size, f32 gui_scaling) { - f32 factor = (g_settings->getBool("enable_touch") ? 10 : 15) / gui_scaling; + f32 factor = (g_settings->getBool("touch_gui") ? 10 : 15) / gui_scaling; f32 ratio = (f32)render_target_size.X / (f32)render_target_size.Y; if (ratio < 1) return { factor, factor / ratio }; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index ccfb12971..8e2411df5 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -97,7 +97,8 @@ void set_default_settings() // Client settings->setDefault("address", ""); settings->setDefault("enable_sound", "true"); - settings->setDefault("enable_touch", bool_to_cstr(has_touch)); + settings->setDefault("touch_controls", bool_to_cstr(has_touch)); + settings->setDefault("touch_gui", bool_to_cstr(has_touch)); settings->setDefault("sound_volume", "0.8"); settings->setDefault("sound_volume_unfocused", "0.3"); settings->setDefault("mute_sound", "false"); diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index a2982fdd2..3f22fb3c4 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -315,7 +315,7 @@ void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element) data->invsize.Y = MYMAX(0, stof(parts[1])); lockSize(false); - if (!g_settings->getBool("enable_touch") && parts.size() == 3) { + if (!g_settings->getBool("touch_gui") && parts.size() == 3) { if (parts[2] == "true") { lockSize(true,v2u32(800,600)); } @@ -3166,7 +3166,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) s32 min_screen_dim = std::min(padded_screensize.X, padded_screensize.Y); double prefer_imgsize; - if (g_settings->getBool("enable_touch")) { + if (g_settings->getBool("touch_gui")) { // The preferred imgsize should be larger to accommodate the // smaller screensize. prefer_imgsize = min_screen_dim / 10 * gui_scaling; diff --git a/src/migratesettings.h b/src/migratesettings.h index 60435f18a..d4488702f 100644 --- a/src/migratesettings.h +++ b/src/migratesettings.h @@ -11,4 +11,12 @@ void migrate_settings() g_settings->getBool("opaque_water") ? "false" : "true"); g_settings->remove("opaque_water"); } + + // Converts enable_touch to touch_controls/touch_gui + if (g_settings->existsLocal("enable_touch")) { + bool value = g_settings->getBool("enable_touch"); + g_settings->setBool("touch_controls", value); + g_settings->setBool("touch_gui", value); + g_settings->remove("enable_touch"); + } } From 7f5a19792c4d27da5f1a072c5d95cc995cc1bbff Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Thu, 8 Aug 2024 09:24:53 +0900 Subject: [PATCH 045/200] enable option to toggle touch controls on Android Signed-off-by: David Heidelberg --- builtin/settingtypes.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index b5aa142b8..ccafde036 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -152,8 +152,6 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false [*Touchscreen] # Enables the touchscreen controls, allowing you to play the game with a touchscreen. -# -# Requires: !android touch_controls (Enable touchscreen controls) bool true # Touchscreen sensitivity multiplier. From 3971b6afcc3dffc0d77c19dc1bb138a8ae0abfc9 Mon Sep 17 00:00:00 2001 From: cx384 Date: Wed, 28 Aug 2024 21:32:31 +0200 Subject: [PATCH 046/200] Main menu: formspec escape world name (#15064) --- builtin/mainmenu/dlg_config_world.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua index e8f49b230..a8c5221de 100644 --- a/builtin/mainmenu/dlg_config_world.lua +++ b/builtin/mainmenu/dlg_config_world.lua @@ -126,7 +126,7 @@ local function get_formspec(data) local retval = "size[11.5,7.5,true]" .. "label[0.5,0;" .. fgettext("World:") .. "]" .. - "label[1.75,0;" .. data.worldspec.name .. "]" + "label[1.75,0;" .. core.formspec_escape(data.worldspec.name) .. "]" if mod.is_modpack or mod.type == "game" then local info = core.formspec_escape( From a6ba5304c41bb66d310b2a2e749ec72b37b608bf Mon Sep 17 00:00:00 2001 From: kromka-chleba <65868699+kromka-chleba@users.noreply.github.com> Date: Sat, 31 Aug 2024 09:43:52 +0000 Subject: [PATCH 047/200] Add new vector utils (ceil, sign, abs, random_in_area) (#14807) --- .luacheckrc | 8 ++++- builtin/common/math.lua | 41 +++++++++++++++++++++ builtin/common/misc_helpers.lua | 42 +--------------------- builtin/common/tests/after_spec.lua | 1 + builtin/common/tests/math_spec.lua | 16 +++++++++ builtin/common/tests/misc_helpers_spec.lua | 1 + builtin/common/tests/vector_spec.lua | 36 +++++++++++++++++++ builtin/common/vector.lua | 35 +++++++++++++----- builtin/init.lua | 1 + doc/lua_api.md | 14 +++++++- 10 files changed, 143 insertions(+), 52 deletions(-) create mode 100644 builtin/common/math.lua create mode 100644 builtin/common/tests/math_spec.lua diff --git a/.luacheckrc b/.luacheckrc index f184e6d59..afc136c7c 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -37,6 +37,12 @@ files["builtin/client/register.lua"] = { } } +files["builtin/common/math.lua"] = { + globals = { + "math", + }, +} + files["builtin/common/misc_helpers.lua"] = { globals = { "dump", "dump2", "table", "math", "string", @@ -46,7 +52,7 @@ files["builtin/common/misc_helpers.lua"] = { } files["builtin/common/vector.lua"] = { - globals = { "vector" }, + globals = { "vector", "math" }, } files["builtin/game/voxelarea.lua"] = { diff --git a/builtin/common/math.lua b/builtin/common/math.lua new file mode 100644 index 000000000..84c6c619b --- /dev/null +++ b/builtin/common/math.lua @@ -0,0 +1,41 @@ +--[[ + Math utils. +--]] + +function math.hypot(x, y) + return math.sqrt(x * x + y * y) +end + +function math.sign(x, tolerance) + tolerance = tolerance or 0 + if x > tolerance then + return 1 + elseif x < -tolerance then + return -1 + end + return 0 +end + +function math.factorial(x) + assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer") + if x >= 171 then + -- 171! is greater than the biggest double, no need to calculate + return math.huge + end + local v = 1 + for k = 2, x do + v = v * k + end + return v +end + +function math.round(x) + if x < 0 then + local int = math.ceil(x) + local frac = x - int + return int - ((frac <= -0.5) and 1 or 0) + end + local int = math.floor(x) + local frac = x - int + return int + ((frac >= 0.5) and 1 or 0) +end diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 21752ce7f..9c25b826f 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -3,6 +3,7 @@ -------------------------------------------------------------------------------- -- Localize functions to avoid table lookups (better performance). local string_sub, string_find = string.sub, string.find +local math = math -------------------------------------------------------------------------------- local function basic_dump(o) @@ -220,47 +221,6 @@ function string:trim() return self:match("^%s*(.-)%s*$") end --------------------------------------------------------------------------------- -function math.hypot(x, y) - return math.sqrt(x * x + y * y) -end - --------------------------------------------------------------------------------- -function math.sign(x, tolerance) - tolerance = tolerance or 0 - if x > tolerance then - return 1 - elseif x < -tolerance then - return -1 - end - return 0 -end - --------------------------------------------------------------------------------- -function math.factorial(x) - assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer") - if x >= 171 then - -- 171! is greater than the biggest double, no need to calculate - return math.huge - end - local v = 1 - for k = 2, x do - v = v * k - end - return v -end - -function math.round(x) - if x < 0 then - local int = math.ceil(x) - local frac = x - int - return int - ((frac <= -0.5) and 1 or 0) - end - local int = math.floor(x) - local frac = x - int - return int + ((frac >= 0.5) and 1 or 0) -end - local formspec_escapes = { ["\\"] = "\\\\", ["["] = "\\[", diff --git a/builtin/common/tests/after_spec.lua b/builtin/common/tests/after_spec.lua index cca1871a5..f23bbd70d 100644 --- a/builtin/common/tests/after_spec.lua +++ b/builtin/common/tests/after_spec.lua @@ -1,6 +1,7 @@ _G.core = {} _G.vector = {metatable = {}} +dofile("builtin/common/math.lua") dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") diff --git a/builtin/common/tests/math_spec.lua b/builtin/common/tests/math_spec.lua new file mode 100644 index 000000000..108ab2b6a --- /dev/null +++ b/builtin/common/tests/math_spec.lua @@ -0,0 +1,16 @@ +_G.core = {} +dofile("builtin/common/math.lua") + +describe("math", function() + it("round()", function() + assert.equal(0, math.round(0)) + assert.equal(10, math.round(10.3)) + assert.equal(11, math.round(10.5)) + assert.equal(11, math.round(10.7)) + assert.equal(-10, math.round(-10.3)) + assert.equal(-11, math.round(-10.5)) + assert.equal(-11, math.round(-10.7)) + assert.equal(0, math.round(0.49999999999999994)) + assert.equal(0, math.round(-0.49999999999999994)) + end) +end) diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua index 611c4b20f..10e2bf277 100644 --- a/builtin/common/tests/misc_helpers_spec.lua +++ b/builtin/common/tests/misc_helpers_spec.lua @@ -1,4 +1,5 @@ _G.core = {} +dofile("builtin/common/math.lua") dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua index 67dbd2d6b..9a0458be4 100644 --- a/builtin/common/tests/vector_spec.lua +++ b/builtin/common/tests/vector_spec.lua @@ -1,4 +1,5 @@ _G.vector = {} +dofile("builtin/common/math.lua") dofile("builtin/common/vector.lua") describe("vector", function() @@ -113,12 +114,35 @@ describe("vector", function() assert.equal(vector.new(0, 1, -1), a:round()) end) + it("ceil()", function() + local a = vector.new(0.1, 0.9, -0.5) + assert.equal(vector.new(1, 1, 0), vector.ceil(a)) + assert.equal(vector.new(1, 1, 0), a:ceil()) + end) + + it("sign()", function() + local a = vector.new(-120.3, 0, 231.5) + assert.equal(vector.new(-1, 0, 1), vector.sign(a)) + assert.equal(vector.new(-1, 0, 1), a:sign()) + assert.equal(vector.new(0, 0, 1), vector.sign(a, 200)) + assert.equal(vector.new(0, 0, 1), a:sign(200)) + end) + + it("abs()", function() + local a = vector.new(-123.456, 0, 13) + assert.equal(vector.new(123.456, 0, 13), vector.abs(a)) + assert.equal(vector.new(123.456, 0, 13), a:abs()) + end) + it("apply()", function() local i = 0 local f = function(x) i = i + 1 return x + i end + local f2 = function(x, opt1, opt2, opt3) + return x + opt1 + opt2 + opt3 + end local a = vector.new(0.1, 0.9, -0.5) assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil)) assert.equal(vector.new(1, 1, 0), a:apply(math.ceil)) @@ -126,6 +150,9 @@ describe("vector", function() assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs)) assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f)) assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f)) + local b = vector.new(1, 2, 3) + assert.equal(vector.new(4, 5, 6), vector.apply(b, f2, 1, 1, 1)) + assert.equal(vector.new(4, 5, 6), b:apply(f2, 1, 1, 1)) end) it("combine()", function() @@ -469,4 +496,13 @@ describe("vector", function() assert.True(vector.in_area(vector.new(-10, -10, -10), vector.new(-10, -10, -10), vector.new(10, 10, 10))) assert.False(vector.in_area(vector.new(-10, -10, -10), vector.new(10, 10, 10), vector.new(-11, -10, -10))) end) + + it("random_in_area()", function() + local min = vector.new(-100, -100, -100) + local max = vector.new(100, 100, 100) + for i = 1, 1000 do + local random = vector.random_in_area(min, max) + assert.True(vector.in_area(random, min, max)) + end + end) end) diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index be82401c3..fb1d2a7d9 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -5,6 +5,7 @@ Note: The vector.*-functions must be able to accept old vectors that had no meta -- localize functions local setmetatable = setmetatable +local math = math vector = {} @@ -97,18 +98,26 @@ function vector.floor(v) end function vector.round(v) - return fast_new( - math.round(v.x), - math.round(v.y), - math.round(v.z) - ) + return vector.apply(v, math.round) end -function vector.apply(v, func) +function vector.ceil(v) + return vector.apply(v, math.ceil) +end + +function vector.sign(v, tolerance) + return vector.apply(v, math.sign, tolerance) +end + +function vector.abs(v) + return vector.apply(v, math.abs) +end + +function vector.apply(v, func, ...) return fast_new( - func(v.x), - func(v.y), - func(v.z) + func(v.x, ...), + func(v.y, ...), + func(v.z, ...) ) end @@ -387,6 +396,14 @@ function vector.random_direction() return fast_new(x/l, y/l, z/l) end +function vector.random_in_area(min, max) + return fast_new( + math.random(min.x, max.x), + math.random(min.y, max.y), + math.random(min.z, max.z) + ) +end + if rawget(_G, "core") and core.set_read_vector and core.set_push_vector then local function read_vector(v) return v.x, v.y, v.z diff --git a/builtin/init.lua b/builtin/init.lua index 49df70971..415087a54 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -42,6 +42,7 @@ local scriptdir = core.get_builtin_path() local commonpath = scriptdir .. "common" .. DIR_DELIM local asyncpath = scriptdir .. "async" .. DIR_DELIM +dofile(commonpath .. "math.lua") dofile(commonpath .. "vector.lua") dofile(commonpath .. "strict.lua") dofile(commonpath .. "serialize.lua") diff --git a/doc/lua_api.md b/doc/lua_api.md index ae046dd3a..bf714a515 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -3850,12 +3850,20 @@ vectors are written like this: `(x, y, z)`: * If `v` has zero length, returns `(0, 0, 0)`. * `vector.floor(v)`: * Returns a vector, each dimension rounded down. +* `vector.ceil(v)`: + * Returns a vector, each dimension rounded up. * `vector.round(v)`: * Returns a vector, each dimension rounded to nearest integer. * At a multiple of 0.5, rounds away from zero. -* `vector.apply(v, func)`: +* `vector.sign(v, tolerance)`: + * Returns a vector where `math.sign` was called for each component. + * See [Helper functions] for details. +* `vector.abs(v)`: + * Returns a vector with absolute values for each component. +* `vector.apply(v, func, ...)`: * Returns a vector where the function `func` has been applied to each component. + * `...` are optional arguments passed to `func`. * `vector.combine(v, w, func)`: * Returns a vector where the function `func` has combined both components of `v` and `w` for each component @@ -3880,6 +3888,10 @@ vectors are written like this: `(x, y, z)`: * `min` and `max` are inclusive. * If `min` is bigger than `max` on some axis, function always returns false. * You can use `vector.sort` if you have two vectors and don't know which are the minimum and the maximum. +* `vector.random_in_area(min, max)`: + * Returns a random integer position in area formed by `min` and `max` + * `min` and `max` are inclusive. + * You can use `vector.sort` if you have two vectors and don't know which are the minimum and the maximum. For the following functions `x` can be either a vector or a number: From efd7792add70ccfafb14e4b2e3ddd6d52457dd8e Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 31 Aug 2024 11:44:30 +0200 Subject: [PATCH 048/200] Debloat IVideoDriver and IrrlichtDevice includes (#15080) As the project grows, compile time will not go down unless the header mess is cleaned up one by one to only include exactly what's needed. --- irr/include/EVideoTypes.h | 75 +++++++++++++++++++++ irr/include/IVideoDriver.h | 71 ++----------------- irr/include/IrrlichtDevice.h | 7 +- irr/include/SViewFrustum.h | 2 +- irr/src/CIrrDeviceLinux.cpp | 1 + irr/src/CIrrDeviceSDL.cpp | 1 + irr/src/CIrrDeviceStub.cpp | 1 + irr/src/CIrrDeviceWin32.cpp | 1 + irr/src/CSceneCollisionManager.cpp | 1 - src/client/camera.cpp | 1 + src/client/clientmap.cpp | 8 +++ src/client/clientmap.h | 7 +- src/client/imagefilters.cpp | 1 + src/client/render/anaglyph.cpp | 1 + src/client/render/core.h | 5 ++ src/client/render/pipeline.h | 7 ++ src/client/renderingengine.h | 1 + src/client/shadows/dynamicshadows.cpp | 1 + src/client/shadows/dynamicshadowsrender.cpp | 1 + src/client/shadows/dynamicshadowsrender.h | 1 + src/client/shadows/shadowsScreenQuad.cpp | 1 + src/gui/guiAnimatedImage.cpp | 1 + src/gui/guiBox.cpp | 1 + src/gui/guiFormSpecMenu.h | 1 + src/gui/guiInventoryList.cpp | 1 + src/gui/guiKeyChangeMenu.cpp | 1 + src/gui/guiOpenURL.cpp | 1 + src/gui/guiPasswordChange.cpp | 1 + src/gui/guiScene.cpp | 1 + src/gui/guiVolumeChange.cpp | 1 + src/gui/profilergraph.cpp | 1 + src/gui/profilergraph.h | 5 +- src/gui/touchcontrols.cpp | 1 + src/gui/touchcontrols.h | 6 +- src/irrlichttypes_extrabloated.h | 1 - 35 files changed, 139 insertions(+), 79 deletions(-) create mode 100644 irr/include/EVideoTypes.h diff --git a/irr/include/EVideoTypes.h b/irr/include/EVideoTypes.h new file mode 100644 index 000000000..fe90f0652 --- /dev/null +++ b/irr/include/EVideoTypes.h @@ -0,0 +1,75 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#pragma once + +#include "SMaterial.h" // MATERIAL_MAX_TEXTURES + +namespace irr::video +{ + +//! enumeration for geometry transformation states +enum E_TRANSFORMATION_STATE +{ + //! View transformation + ETS_VIEW = 0, + //! World transformation + ETS_WORLD, + //! Projection transformation + ETS_PROJECTION, + //! Texture 0 transformation + //! Use E_TRANSFORMATION_STATE(ETS_TEXTURE_0 + texture_number) to access other texture transformations + ETS_TEXTURE_0, + //! Only used internally + ETS_COUNT = ETS_TEXTURE_0 + MATERIAL_MAX_TEXTURES +}; + +//! Special render targets, which usually map to dedicated hardware +/** These render targets (besides 0 and 1) need not be supported by gfx cards */ +enum E_RENDER_TARGET +{ + //! Render target is the main color frame buffer + ERT_FRAME_BUFFER = 0, + //! Render target is a render texture + ERT_RENDER_TEXTURE, + //! Multi-Render target textures + ERT_MULTI_RENDER_TEXTURES, + //! Render target is the main color frame buffer + ERT_STEREO_LEFT_BUFFER, + //! Render target is the right color buffer (left is the main buffer) + ERT_STEREO_RIGHT_BUFFER, + //! Render to both stereo buffers at once + ERT_STEREO_BOTH_BUFFERS, + //! Auxiliary buffer 0 + ERT_AUX_BUFFER0, + //! Auxiliary buffer 1 + ERT_AUX_BUFFER1, + //! Auxiliary buffer 2 + ERT_AUX_BUFFER2, + //! Auxiliary buffer 3 + ERT_AUX_BUFFER3, + //! Auxiliary buffer 4 + ERT_AUX_BUFFER4 +}; + +//! Enum for the flags of clear buffer +enum E_CLEAR_BUFFER_FLAG +{ + ECBF_NONE = 0, + ECBF_COLOR = 1, + ECBF_DEPTH = 2, + ECBF_STENCIL = 4, + ECBF_ALL = ECBF_COLOR | ECBF_DEPTH | ECBF_STENCIL +}; + +//! Enum for the types of fog distributions to choose from +enum E_FOG_TYPE +{ + EFT_FOG_EXP = 0, + EFT_FOG_LINEAR, + EFT_FOG_EXP2 +}; + +} // irr::video + diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index 7b15fdd1b..6d2182e8a 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -9,14 +9,16 @@ #include "ITexture.h" #include "irrArray.h" #include "matrix4.h" -#include "plane3d.h" #include "dimension2d.h" #include "position2d.h" -#include "IMeshBuffer.h" #include "EDriverTypes.h" #include "EDriverFeatures.h" +#include "EPrimitiveTypes.h" +#include "EVideoTypes.h" #include "SExposedVideoData.h" #include "SOverrideMaterial.h" +#include "S3DVertex.h" // E_VERTEX_TYPE +#include "SVertexIndex.h" // E_INDEX_TYPE namespace irr { @@ -36,77 +38,12 @@ class ISceneNode; namespace video { -struct S3DVertex; -struct S3DVertex2TCoords; -struct S3DVertexTangents; class IImageLoader; class IImageWriter; class IMaterialRenderer; class IGPUProgrammingServices; class IRenderTarget; -//! enumeration for geometry transformation states -enum E_TRANSFORMATION_STATE -{ - //! View transformation - ETS_VIEW = 0, - //! World transformation - ETS_WORLD, - //! Projection transformation - ETS_PROJECTION, - //! Texture 0 transformation - //! Use E_TRANSFORMATION_STATE(ETS_TEXTURE_0 + texture_number) to access other texture transformations - ETS_TEXTURE_0, - //! Only used internally - ETS_COUNT = ETS_TEXTURE_0 + MATERIAL_MAX_TEXTURES -}; - -//! Special render targets, which usually map to dedicated hardware -/** These render targets (besides 0 and 1) need not be supported by gfx cards */ -enum E_RENDER_TARGET -{ - //! Render target is the main color frame buffer - ERT_FRAME_BUFFER = 0, - //! Render target is a render texture - ERT_RENDER_TEXTURE, - //! Multi-Render target textures - ERT_MULTI_RENDER_TEXTURES, - //! Render target is the main color frame buffer - ERT_STEREO_LEFT_BUFFER, - //! Render target is the right color buffer (left is the main buffer) - ERT_STEREO_RIGHT_BUFFER, - //! Render to both stereo buffers at once - ERT_STEREO_BOTH_BUFFERS, - //! Auxiliary buffer 0 - ERT_AUX_BUFFER0, - //! Auxiliary buffer 1 - ERT_AUX_BUFFER1, - //! Auxiliary buffer 2 - ERT_AUX_BUFFER2, - //! Auxiliary buffer 3 - ERT_AUX_BUFFER3, - //! Auxiliary buffer 4 - ERT_AUX_BUFFER4 -}; - -//! Enum for the flags of clear buffer -enum E_CLEAR_BUFFER_FLAG -{ - ECBF_NONE = 0, - ECBF_COLOR = 1, - ECBF_DEPTH = 2, - ECBF_STENCIL = 4, - ECBF_ALL = ECBF_COLOR | ECBF_DEPTH | ECBF_STENCIL -}; - -//! Enum for the types of fog distributions to choose from -enum E_FOG_TYPE -{ - EFT_FOG_EXP = 0, - EFT_FOG_LINEAR, - EFT_FOG_EXP2 -}; - const c8 *const FogTypeNames[] = { "FogExp", "FogLinear", diff --git a/irr/include/IrrlichtDevice.h b/irr/include/IrrlichtDevice.h index 11619010c..777a23420 100644 --- a/irr/include/IrrlichtDevice.h +++ b/irr/include/IrrlichtDevice.h @@ -6,14 +6,16 @@ #include "IReferenceCounted.h" #include "dimension2d.h" -#include "IVideoDriver.h" #include "EDriverTypes.h" #include "EDeviceTypes.h" #include "IEventReceiver.h" #include "ICursorControl.h" #include "ITimer.h" #include "IOSOperator.h" +#include "irrArray.h" #include "IrrCompileConfig.h" +#include "position2d.h" +#include "SColor.h" // video::ECOLOR_FORMAT namespace irr { @@ -38,6 +40,9 @@ class ISceneManager; namespace video { class IContextManager; +class IImage; +class ITexture; +class IVideoDriver; extern "C" IRRLICHT_API bool IRRCALLCONV isDriverSupported(E_DRIVER_TYPE driver); } // end namespace video diff --git a/irr/include/SViewFrustum.h b/irr/include/SViewFrustum.h index 06983cc5e..cd898e032 100644 --- a/irr/include/SViewFrustum.h +++ b/irr/include/SViewFrustum.h @@ -9,7 +9,7 @@ #include "line3d.h" #include "aabbox3d.h" #include "matrix4.h" -#include "IVideoDriver.h" +#include "EVideoTypes.h" namespace irr { diff --git a/irr/src/CIrrDeviceLinux.cpp b/irr/src/CIrrDeviceLinux.cpp index 5e3afae36..6ecb499b2 100644 --- a/irr/src/CIrrDeviceLinux.cpp +++ b/irr/src/CIrrDeviceLinux.cpp @@ -26,6 +26,7 @@ #include "IGUISpriteBank.h" #include "IImageLoader.h" #include "IFileSystem.h" +#include "IVideoDriver.h" #include #include diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index 58fba4f25..14d996e47 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -10,6 +10,7 @@ #include "IGUIEnvironment.h" #include "IImageLoader.h" #include "IFileSystem.h" +#include "IVideoDriver.h" #include "os.h" #include "CTimer.h" #include "irrString.h" diff --git a/irr/src/CIrrDeviceStub.cpp b/irr/src/CIrrDeviceStub.cpp index 1ff120d10..fd8e458c8 100644 --- a/irr/src/CIrrDeviceStub.cpp +++ b/irr/src/CIrrDeviceStub.cpp @@ -8,6 +8,7 @@ #include "IFileSystem.h" #include "IGUIElement.h" #include "IGUIEnvironment.h" +#include "IVideoDriver.h" #include "os.h" #include "CTimer.h" #include "CLogger.h" diff --git a/irr/src/CIrrDeviceWin32.cpp b/irr/src/CIrrDeviceWin32.cpp index fe5293988..366be8013 100644 --- a/irr/src/CIrrDeviceWin32.cpp +++ b/irr/src/CIrrDeviceWin32.cpp @@ -17,6 +17,7 @@ #include "COSOperator.h" #include "dimension2d.h" #include "IGUISpriteBank.h" +#include "IVideoDriver.h" #include #include "SExposedVideoData.h" diff --git a/irr/src/CSceneCollisionManager.cpp b/irr/src/CSceneCollisionManager.cpp index 692f3c44f..77549a7dc 100644 --- a/irr/src/CSceneCollisionManager.cpp +++ b/irr/src/CSceneCollisionManager.cpp @@ -6,7 +6,6 @@ #include "ICameraSceneNode.h" #include "SViewFrustum.h" -#include "os.h" #include "irrMath.h" namespace irr diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 5e724d05e..f13046848 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "script/scripting_client.h" #include "gettext.h" #include +#include #define CAMERA_OFFSET_STEP 200 #define WIELDMESH_OFFSET_X 55.0f diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index cdad3146c..7d40dec92 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/mesh.h" #include "mapblock_mesh.h" #include +#include #include #include "mapsector.h" #include "mapblock.h" @@ -192,6 +193,13 @@ void ClientMap::OnRegisterSceneNode() // we have other way to find it } +void ClientMap::render() +{ + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); + renderMap(driver, SceneManager->getSceneNodeRenderPass()); +} + void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes, v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range) { diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 7456902c8..8fac5a471 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -75,12 +75,7 @@ public: virtual void OnRegisterSceneNode() override; - virtual void render() override - { - video::IVideoDriver* driver = SceneManager->getVideoDriver(); - driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); - renderMap(driver, SceneManager->getSceneNodeRenderPass()); - } + virtual void render() override; virtual const aabb3f &getBoundingBox() const override { diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index db6523ad3..57e444151 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include // Simple 2D bitmap class with just the functionality needed here class Bitmap { diff --git a/src/client/render/anaglyph.cpp b/src/client/render/anaglyph.cpp index 4cb42db50..b1117998f 100644 --- a/src/client/render/anaglyph.cpp +++ b/src/client/render/anaglyph.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "anaglyph.h" #include "client/camera.h" +#include /// SetColorMaskStep step diff --git a/src/client/render/core.h b/src/client/render/core.h index c5617bcb2..fb60b274e 100644 --- a/src/client/render/core.h +++ b/src/client/render/core.h @@ -21,6 +21,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" +namespace irr +{ + class IrrlichtDevice; +} + class ShadowRenderer; class Camera; class Client; diff --git a/src/client/render/pipeline.h b/src/client/render/pipeline.h index abb108652..d0e3b2c38 100644 --- a/src/client/render/pipeline.h +++ b/src/client/render/pipeline.h @@ -19,6 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" +#include // used in all render/*.cpp +#include // used in all render/*.cpp #include #include @@ -31,6 +33,11 @@ class Client; class Hud; class ShadowRenderer; +namespace irr::video +{ + class IRenderTarget; +} + struct PipelineContext { PipelineContext(IrrlichtDevice *_device, Client *_client, Hud *_hud, ShadowRenderer *_shadow_renderer, video::SColor _color, v2u32 _target_size) diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 1a2a63513..7f7518f61 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/render/core.h" // include the shadow mapper classes too #include "client/shadows/dynamicshadowsrender.h" +#include #ifdef SERVER #error Do not include in server builds diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp index 11db9bea7..ffe7d4de5 100644 --- a/src/client/shadows/dynamicshadows.cpp +++ b/src/client/shadows/dynamicshadows.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/clientenvironment.h" #include "client/clientmap.h" #include "client/camera.h" +#include using m4f = core::matrix4; diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 91992bc08..cfa54dfb7 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "EShaderTypes.h" #include "IGPUProgrammingServices.h" #include "IMaterialRenderer.h" +#include ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : m_smgr(device->getSceneManager()), m_driver(device->getVideoDriver()), diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index fc139e28b..c4ffb39e2 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#include #include "irrlichttypes_extrabloated.h" #include "client/shadows/dynamicshadows.h" diff --git a/src/client/shadows/shadowsScreenQuad.cpp b/src/client/shadows/shadowsScreenQuad.cpp index 5f6d38157..f0eb9ab4a 100644 --- a/src/client/shadows/shadowsScreenQuad.cpp +++ b/src/client/shadows/shadowsScreenQuad.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "shadowsScreenQuad.h" +#include shadowScreenQuad::shadowScreenQuad() { diff --git a/src/gui/guiAnimatedImage.cpp b/src/gui/guiAnimatedImage.cpp index 5e14d2ef2..1c13531dc 100644 --- a/src/gui/guiAnimatedImage.cpp +++ b/src/gui/guiAnimatedImage.cpp @@ -6,6 +6,7 @@ #include "util/string.h" #include #include +#include GUIAnimatedImage::GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, const core::rect &rectangle) : diff --git a/src/gui/guiBox.cpp b/src/gui/guiBox.cpp index 443f1064f..972eb4538 100644 --- a/src/gui/guiBox.cpp +++ b/src/gui/guiBox.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "guiBox.h" +#include GUIBox::GUIBox(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, const core::rect &rectangle, diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 8c4fda32e..30045a168 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -37,6 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "util/enriched_string.h" #include "StyleSpec.h" +#include // gui::ECURSOR_ICON #include class InventoryManager; diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp index 02505d436..e5ed6e6ef 100644 --- a/src/gui/guiInventoryList.cpp +++ b/src/gui/guiInventoryList.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiFormSpecMenu.h" #include "client/hud.h" #include "client/client.h" +#include GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env, gui::IGUIElement *parent, diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index 97e1ae8e0..08ae5987e 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "settings.h" #include diff --git a/src/gui/guiOpenURL.cpp b/src/gui/guiOpenURL.cpp index f91d31391..7ce79b0e5 100644 --- a/src/gui/guiOpenURL.cpp +++ b/src/gui/guiOpenURL.cpp @@ -20,6 +20,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "guiEditBoxWithScrollbar.h" #include #include +#include #include "client/renderingengine.h" #include "porting.h" #include "gettext.h" diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp index 414ff36a8..a9b73dfe8 100644 --- a/src/gui/guiPasswordChange.cpp +++ b/src/gui/guiPasswordChange.cpp @@ -24,6 +24,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include +#include #include "porting.h" #include "gettext.h" diff --git a/src/gui/guiScene.cpp b/src/gui/guiScene.cpp index 239fbe015..a26cfa93f 100644 --- a/src/gui/guiScene.cpp +++ b/src/gui/guiScene.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#include #include "porting.h" GUIScene::GUIScene(gui::IGUIEnvironment *env, scene::ISceneManager *smgr, diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp index a6608dd18..4d1139dce 100644 --- a/src/gui/guiVolumeChange.cpp +++ b/src/gui/guiVolumeChange.cpp @@ -27,6 +27,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include +#include #include "settings.h" #include "gettext.h" diff --git a/src/gui/profilergraph.cpp b/src/gui/profilergraph.cpp index e6fdf9ae8..ab4796cb9 100644 --- a/src/gui/profilergraph.cpp +++ b/src/gui/profilergraph.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "profilergraph.h" +#include "IVideoDriver.h" #include "util/string.h" void ProfilerGraph::put(const Profiler::GraphValues &values) diff --git a/src/gui/profilergraph.h b/src/gui/profilergraph.h index 6354ac9ef..c92cbf837 100644 --- a/src/gui/profilergraph.h +++ b/src/gui/profilergraph.h @@ -23,9 +23,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include -#include #include "profiler.h" +namespace irr::video { + class IVideoDriver; +} + /* Profiler display */ class ProfilerGraph { diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index b96ced1bf..1e4f8a99b 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettext.h" #include "IGUIStaticText.h" #include "IGUIFont.h" +#include #include #include diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index ebc251bb6..0a86fe34e 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include -#include #include #include @@ -35,6 +34,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemdef.h" #include "client/game.h" +namespace irr +{ + class IrrlichtDevice; +} + using namespace irr; using namespace irr::core; using namespace irr::gui; diff --git a/src/irrlichttypes_extrabloated.h b/src/irrlichttypes_extrabloated.h index b03ba7955..a3de2c3c8 100644 --- a/src/irrlichttypes_extrabloated.h +++ b/src/irrlichttypes_extrabloated.h @@ -24,7 +24,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef SERVER #include #include -#include #include #include #include From 52376fd87a0cebb75bcd99113deed0fd569e6786 Mon Sep 17 00:00:00 2001 From: cx384 Date: Mon, 29 Jan 2024 16:43:37 +0100 Subject: [PATCH 049/200] Add hotbar Lua HUD element and replace hardcoded hotbar --- builtin/game/features.lua | 1 + builtin/game/hud.lua | 15 +++++ doc/lua_api.md | 16 +++++- games/devtest/mods/testhud/init.lua | 89 +++++++++++++++++++++++++++++ src/client/hud.cpp | 54 ++++++++++------- src/client/hud.h | 4 +- src/client/render/plain.cpp | 1 - src/hud.cpp | 1 + src/hud.h | 3 +- src/network/networkprotocol.h | 7 ++- src/script/common/c_content.cpp | 5 +- 11 files changed, 165 insertions(+), 31 deletions(-) diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 22bf1859d..358ee8ff0 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -42,6 +42,7 @@ core.features = { node_interaction_actor = true, moveresult_new_pos = true, override_item_remove_fields = true, + hotbar_hud_element = true, } function core.has_feature(arg) diff --git a/builtin/game/hud.lua b/builtin/game/hud.lua index 1bc4b48d7..a2a046848 100644 --- a/builtin/game/hud.lua +++ b/builtin/game/hud.lua @@ -259,3 +259,18 @@ register_builtin_hud_element("minimap", { core.get_player_information(player:get_player_name()).protocol_version >= 44 end, }) + +--- Hotbar + +register_builtin_hud_element("hotbar", { + elem_def = { + type = "hotbar", + position = {x = 0.5, y = 1}, + direction = 0, + alignment = {x = 0, y = -1}, + offset = {x = 0, y = -4}, -- Extra padding below. + }, + show_elem = function(player, flags) + return flags.hotbar + end, +}) diff --git a/doc/lua_api.md b/doc/lua_api.md index bf714a515..2e9c57c9e 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -1744,6 +1744,13 @@ Displays a horizontal bar made up of half-images with an optional background. * `item`: Position of item that is selected. * `direction`: Direction the list will be displayed in * `offset`: offset in pixels from position. +* `alignment`: The alignment of the inventory. Aligned at the top left corner if not specified. + +### `hotbar` + +* `direction`: Direction the list will be displayed in +* `offset`: offset in pixels from position. +* `alignment`: The alignment of the inventory. ### `waypoint` @@ -5471,6 +5478,8 @@ Utilities moveresult_new_pos = true, -- Allow removing definition fields in `minetest.override_item` (5.9.0) override_item_remove_fields = true, + -- The predefined hotbar is a Lua HUD element of type `hotbar` (5.10.0) + hotbar_hud_element = true, } ``` @@ -7117,7 +7126,7 @@ Misc. could be used as a player name (regardless of whether said player exists). * `minetest.hud_replace_builtin(name, hud_definition)` * Replaces definition of a builtin hud element - * `name`: `"breath"`, `"health"` or `"minimap"` + * `name`: `"breath"`, `"health"`, `"minimap"` or `"hotbar"` * `hud_definition`: definition to replace builtin definition * `minetest.parse_relative_number(arg, relative_to)`: returns number or nil * Helper function for chat commands. @@ -10664,8 +10673,9 @@ Used by `ObjectRef:hud_add`. Returned by `ObjectRef:hud_get`. ```lua { type = "image", - -- Type of element, can be "image", "text", "statbar", "inventory", - -- "waypoint", "image_waypoint", "compass" or "minimap" + -- Type of element, can be "compass", "hotbar" (46 ¹), "image", "image_waypoint", + -- "inventory", "minimap" (44 ¹), "statbar", "text" or "waypoint" + -- ¹: minimal protocol version for client-side support -- If undefined "text" will be used. hud_elem_type = "image", diff --git a/games/devtest/mods/testhud/init.lua b/games/devtest/mods/testhud/init.lua index 9afed8fc7..1788ad77e 100644 --- a/games/devtest/mods/testhud/init.lua +++ b/games/devtest/mods/testhud/init.lua @@ -208,9 +208,75 @@ minetest.register_chatcommand("zoomfov", { end, }) +-- Hotbars + +local hud_hotbar_defs = { + { + type = "hotbar", + position = {x=0.2, y=0.5}, + direction = 0, + alignment = {x=1, y=-1}, + }, + { + type = "hotbar", + position = {x=0.2, y=0.5}, + direction = 2, + alignment = {x=1, y=1}, + }, + { + type = "hotbar", + position = {x=0.7, y=0.5}, + direction = 0, + offset = {x=140, y=20}, + alignment = {x=-1, y=-1}, + }, + { + type = "hotbar", + position = {x=0.7, y=0.5}, + direction = 2, + offset = {x=140, y=20}, + alignment = {x=-1, y=1}, + }, +} + + +local player_hud_hotbars= {} +minetest.register_chatcommand("hudhotbars", { + description = "Shows some test Lua HUD elements of type hotbar. " .. + "(add: Adds elements (default). remove: Removes elements)", + params = "[ add | remove ]", + func = function(name, params) + local player = minetest.get_player_by_name(name) + if not player then + return false, "No player." + end + + local id_table = player_hud_hotbars[name] + if not id_table then + id_table = {} + player_hud_hotbars[name] = id_table + end + + if params == "remove" then + for _, id in ipairs(id_table) do + player:hud_remove(id) + end + return true, "Hotbars removed." + end + + -- params == "add" or default + for _, def in ipairs(hud_hotbar_defs) do + table.insert(id_table, player:hud_add(def)) + end + return true, #hud_hotbar_defs .." Hotbars added." + end +}) + + minetest.register_on_leaveplayer(function(player) player_font_huds[player:get_player_name()] = nil player_waypoints[player:get_player_name()] = nil + player_hud_hotbars[player:get_player_name()] = nil end) minetest.register_chatcommand("hudprint", { @@ -232,3 +298,26 @@ minetest.register_chatcommand("hudprint", { return true, s end }) + +local hud_flags = {"hotbar", "healthbar", "crosshair", "wielditem", "breathbar", + "minimap", "minimap_radar", "basic_debug", "chat"} + +minetest.register_chatcommand("hudtoggleflag", { + description = "Toggles a hud flag.", + params = "[ ".. table.concat(hud_flags, " | ") .." ]", + func = function(name, params) + local player = minetest.get_player_by_name(name) + if not player then + return false, "No player." + end + + local flags = player:hud_get_flags() + if flags[params] == nil then + return false, "Unknown hud flag." + end + + flags[params] = not flags[params] + player:hud_set_flags(flags) + return true, "Flag \"".. params .."\" set to ".. tostring(flags[params]) .. "." + end +}) diff --git a/src/client/hud.cpp b/src/client/hud.cpp index e2d1ee455..261d01c8e 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -255,7 +255,7 @@ void Hud::drawItem(const ItemStack &item, const core::rect& rect, // NOTE: selectitem = 0 -> no selected; selectitem is 1-based // mainlist can be NULL, but draw the frame anyway. -void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, +void Hud::drawItems(v2s32 screen_pos, v2s32 screen_offset, s32 itemcount, v2f alignment, s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction, bool is_hotbar) { @@ -268,9 +268,11 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, width = tmp; } - // Position of upper left corner of bar - v2s32 pos = screen_offset * m_scale_factor; - pos += upperleftpos; + // Position: screen_pos + screen_offset + alignment + v2s32 pos(screen_offset.X * m_scale_factor, screen_offset.Y * m_scale_factor); + pos += screen_pos; + pos.X += (alignment.X - 1.0f) * (width * 0.5f); + pos.Y += (alignment.Y - 1.0f) * (height * 0.5f); // Store hotbar_image in member variable, used by drawItem() if (hotbar_image != player->hotbar_image) { @@ -368,13 +370,20 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) std::vector elems; elems.reserve(player->maxHudId()); - // Add builtin minimap if the server doesn't send it. + // Add builtin elements if the server doesn't send them. + // Declared here such that they have the same lifetime as the elems vector HudElement minimap; + HudElement hotbar; if (client->getProtoVersion() < 44 && (player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) { minimap = {HUD_ELEM_MINIMAP, v2f(1, 0), "", v2f(), "", 0 , 0, 0, v2f(-1, 1), v2f(-10, 10), v3f(), v2s32(256, 256), 0, "", 0}; elems.push_back(&minimap); } + if (client->getProtoVersion() < 46 && player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) { + hotbar = {HUD_ELEM_HOTBAR, v2f(0.5, 1), "", v2f(), "", 0 , 0, 0, v2f(-0.5, -1), + v2f(0, -4), v3f(), v2s32(), 0, "", 0}; + elems.push_back(&hotbar); + } for (size_t i = 0; i != player->maxHudId(); i++) { HudElement *e = player->getHud(i); @@ -449,7 +458,7 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) InventoryList *inv = inventory->getList(e->text); if (!inv) warningstream << "HUD: Unknown inventory list. name=" << e->text << std::endl; - drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0, + drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, e->align, 0, inv, e->item, e->dir, false); break; } case HUD_ELEM_WAYPOINT: { @@ -574,6 +583,9 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) e->offset.Y * m_scale_factor); client->getMinimap()->drawMinimap(rect); break; } + case HUD_ELEM_HOTBAR: { + drawHotbar(pos, e->offset, e->dir, e->align); + break; } default: infostream << "Hud::drawLuaElements: ignoring drawform " << e->type << " due to unrecognized type" << std::endl; @@ -783,9 +795,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, } } } - - -void Hud::drawHotbar(u16 playeritem) +void Hud::drawHotbar(const v2s32 &pos, const v2f &offset, u16 dir, const v2f &align) { if (g_touchcontrols) g_touchcontrols->resetHotbarRects(); @@ -796,30 +806,30 @@ void Hud::drawHotbar(u16 playeritem) return; } + u16 playeritem = player->getWieldIndex(); + v2s32 screen_offset(offset.X, offset.Y); + v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y); s32 hotbar_itemcount = player->getMaxHotbarItemcount(); s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2); - v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3); const v2u32 &window_size = RenderingEngine::getWindowSize(); if ((float) width / (float) window_size.X <= g_settings->getFloat("hud_hotbar_max_width")) { - if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) { - drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0, true); - } + drawItems(pos, screen_offset, hotbar_itemcount, align, 0, + mainlist, playeritem + 1, dir, true); } else { - pos.X += width/4; + v2s32 firstpos = pos; + firstpos.X += width/4; - v2s32 secondpos = pos; - pos = pos - v2s32(0, m_hotbar_imagesize + m_padding); + v2s32 secondpos = firstpos; + firstpos = firstpos - v2s32(0, m_hotbar_imagesize + m_padding); - if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) { - drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0, - mainlist, playeritem + 1, 0, true); - drawItems(secondpos, v2s32(0, 0), hotbar_itemcount, - hotbar_itemcount / 2, mainlist, playeritem + 1, 0, true); - } + drawItems(firstpos, screen_offset, hotbar_itemcount / 2, align, 0, + mainlist, playeritem + 1, dir, true); + drawItems(secondpos, screen_offset, hotbar_itemcount, align, + hotbar_itemcount / 2, mainlist, playeritem + 1, dir, true); } } diff --git a/src/client/hud.h b/src/client/hud.h index 3a9d27022..07df1c66b 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -63,7 +63,7 @@ public: void disableBlockBounds(); void drawBlockBounds(); - void drawHotbar(u16 playeritem); + void drawHotbar(const v2s32 &pos, const v2f &offset, u16 direction, const v2f &align); void resizeHotbar(); void drawCrosshair(); void drawSelectionMesh(); @@ -99,7 +99,7 @@ private: const std::string &texture, const std::string& bgtexture, s32 count, s32 maxcount, v2s32 offset, v2s32 size = v2s32()); - void drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount, + void drawItems(v2s32 screen_pos, v2s32 screen_offset, s32 itemcount, v2f alignment, s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction, bool is_hotbar); diff --git a/src/client/render/plain.cpp b/src/client/render/plain.cpp index 60a732415..0ca2476ee 100644 --- a/src/client/render/plain.cpp +++ b/src/client/render/plain.cpp @@ -61,7 +61,6 @@ void DrawHUD::run(PipelineContext &context) if (context.draw_crosshair) context.hud->drawCrosshair(); - context.hud->drawHotbar(context.client->getEnv().getLocalPlayer()->getWieldIndex()); context.hud->drawLuaElements(context.client->getCamera()->getOffset()); context.client->getCamera()->drawNametags(); } diff --git a/src/hud.cpp b/src/hud.cpp index 9b336cdef..e66eff1e5 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -30,6 +30,7 @@ const struct EnumString es_HudElementType[] = {HUD_ELEM_IMAGE_WAYPOINT, "image_waypoint"}, {HUD_ELEM_COMPASS, "compass"}, {HUD_ELEM_MINIMAP, "minimap"}, + {HUD_ELEM_HOTBAR, "hotbar"}, {0, NULL}, }; diff --git a/src/hud.h b/src/hud.h index 5540828d4..d32ab784e 100644 --- a/src/hud.h +++ b/src/hud.h @@ -67,7 +67,8 @@ enum HudElementType { HUD_ELEM_WAYPOINT = 4, HUD_ELEM_IMAGE_WAYPOINT = 5, HUD_ELEM_COMPASS = 6, - HUD_ELEM_MINIMAP = 7 + HUD_ELEM_MINIMAP = 7, + HUD_ELEM_HOTBAR = 8, }; enum HudElementStat : u8 { diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 743aac831..0ad82835e 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -224,9 +224,14 @@ with this program; if not, write to the Free Software Foundation, Inc., Add TOCLIENT_MOVE_PLAYER_REL Move default minimap from client-side C++ to server-side builtin Lua [scheduled bump for 5.9.0] + PROTOCOL VERSION 45: + Reserved for minimap size change + PROTOCOL VERSION 46: + Move default hotbar from client-side C++ to server-side builtin Lua + [scheduled bump for 5.10.0] */ -#define LATEST_PROTOCOL_VERSION 44 +#define LATEST_PROTOCOL_VERSION 46 #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) // Server's supported network protocol range diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index bd76a3ad3..2e5873bbf 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -2306,7 +2306,10 @@ void read_hud_element(lua_State *L, HudElement *elem) elem->dir = getintfield_default(L, 2, "dir", 0); lua_getfield(L, 2, "alignment"); - elem->align = lua_istable(L, -1) ? read_v2f(L, -1) : v2f(); + if (lua_istable(L, -1)) + elem->align = read_v2f(L, -1); + else + elem->align = elem->type == HUD_ELEM_INVENTORY ? v2f(1.0f, 1.0f) : v2f(); lua_pop(L, 1); lua_getfield(L, 2, "offset"); From 322a9c2f74a83603b709497c34ca03edf43c7bfd Mon Sep 17 00:00:00 2001 From: grorp Date: Sat, 31 Aug 2024 18:11:56 +0200 Subject: [PATCH 050/200] Restore proportional minimap scaling (#15022) --- builtin/game/hud.lua | 11 ++++++++--- doc/lua_api.md | 5 +++++ src/client/hud.cpp | 13 ++++++++++--- src/network/networkprotocol.h | 3 ++- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/builtin/game/hud.lua b/builtin/game/hud.lua index a2a046848..eaf037e68 100644 --- a/builtin/game/hud.lua +++ b/builtin/game/hud.lua @@ -251,12 +251,17 @@ register_builtin_hud_element("minimap", { position = {x = 1, y = 0}, alignment = {x = -1, y = 1}, offset = {x = -10, y = 10}, - size = {x = 256, y = 256}, + size = {x = 0, y = -25}, }, show_elem = function(player, flags) + local proto_ver = core.get_player_information(player:get_player_name()).protocol_version -- Don't add a minimap for clients which already have it hardcoded in C++. - return flags.minimap and - core.get_player_information(player:get_player_name()).protocol_version >= 44 + return flags.minimap and proto_ver >= 44 + end, + update_def = function(player, elem_def) + local proto_ver = core.get_player_information(player:get_player_name()).protocol_version + -- Only use percentage when the client supports it. + elem_def.size = proto_ver >= 45 and {x = 0, y = -25} or {x = 256, y = 256} end, }) diff --git a/doc/lua_api.md b/doc/lua_api.md index 2e9c57c9e..e6a351918 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -1809,6 +1809,11 @@ Displays a minimap on the HUD. * `size`: Size of the minimap to display. Minimap should be a square to avoid distortion. + * Negative values represent percentages of the screen. If either `x` or `y` + is specified as a percentage, the resulting pixel size will be used for + both `x` and `y`. Example: On a 1920x1080 screen, `{x = 0, y = -25}` will + result in a 270x270 minimap. + * Negative values are supported starting with protocol version 45. * `alignment`: The alignment of the minimap. * `offset`: offset in pixels from position. diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 261d01c8e..007421f7a 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -568,14 +568,21 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) } break; } case HUD_ELEM_MINIMAP: { - if (e->size.X <= 0 || e->size.Y <= 0) - break; if (!client->getMinimap()) break; // Draw a minimap of size "size" v2s32 dstsize(e->size.X * m_scale_factor, e->size.Y * m_scale_factor); - // (no percent size as minimap would likely be anamorphosed) + + // Only one percentage is supported to avoid distortion. + if (e->size.X < 0) + dstsize.X = dstsize.Y = m_screensize.X * (e->size.X * -0.01); + else if (e->size.Y < 0) + dstsize.X = dstsize.Y = m_screensize.Y * (e->size.Y * -0.01); + + if (dstsize.X <= 0 || dstsize.Y <= 0) + return; + v2s32 offset((e->align.X - 1.0) * dstsize.X / 2, (e->align.Y - 1.0) * dstsize.Y / 2); core::rect rect(0, 0, dstsize.X, dstsize.Y); diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 0ad82835e..467124f60 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -225,7 +225,8 @@ with this program; if not, write to the Free Software Foundation, Inc., Move default minimap from client-side C++ to server-side builtin Lua [scheduled bump for 5.9.0] PROTOCOL VERSION 45: - Reserved for minimap size change + Minimap HUD element supports negative size values as percentages + [bump for 5.9.1] PROTOCOL VERSION 46: Move default hotbar from client-side C++ to server-side builtin Lua [scheduled bump for 5.10.0] From eae9a70385d0cafde1df178c7be12f33dd09689d Mon Sep 17 00:00:00 2001 From: grorp Date: Sat, 31 Aug 2024 20:45:32 +0200 Subject: [PATCH 051/200] TouchControls: Fix outdated player controls in TOSERVER_INTERACT --- src/client/game.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/game.cpp b/src/client/game.cpp index 53871d5ac..0a9b4ec58 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3358,6 +3358,10 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) if (g_touchcontrols) { auto mode = selected_def.touch_interaction.getMode(pointed.type); g_touchcontrols->applyContextControls(mode); + // applyContextControls may change dig/place input. + // Update again so that TOSERVER_INTERACT packets have the correct controls set. + player->control.dig = isKeyDown(KeyType::DIG); + player->control.place = isKeyDown(KeyType::PLACE); } // Note that updating the selection mesh every frame is not particularly efficient, From 5c171f6d61b6ddab0d0d28482bfabb6e3564197e Mon Sep 17 00:00:00 2001 From: grorp Date: Sat, 31 Aug 2024 20:45:53 +0200 Subject: [PATCH 052/200] Basic unittest for HP change calculation --- games/devtest/mods/unittests/player.lua | 44 ++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/games/devtest/mods/unittests/player.lua b/games/devtest/mods/unittests/player.lua index 0dbe450b0..7650d5f57 100644 --- a/games/devtest/mods/unittests/player.lua +++ b/games/devtest/mods/unittests/player.lua @@ -2,7 +2,7 @@ -- HP Change Reasons -- local expect = nil -minetest.register_on_player_hpchange(function(player, hp, reason) +core.register_on_player_hpchange(function(player, hp_change, reason) if expect == nil then return end @@ -37,6 +37,48 @@ local function run_hpchangereason_tests(player) end unittests.register("test_hpchangereason", run_hpchangereason_tests, {player=true}) +-- +-- HP differences +-- + +local expected_diff = nil +core.register_on_player_hpchange(function(player, hp_change, reason) + if expected_diff then + assert(hp_change == expected_diff) + end +end) + +local function run_hp_difference_tests(player) + local old_hp = player:get_hp() + local old_hp_max = player:get_properties().hp_max + + expected_diff = nil + player:set_properties({hp_max = 30}) + player:set_hp(22) + + -- final HP value is clamped to >= 0 before difference calculation + expected_diff = -22 + player:set_hp(-3) + -- and actual final HP value is clamped to >= 0 too + assert(player:get_hp() == 0) + + expected_diff = 22 + player:set_hp(22) + assert(player:get_hp() == 22) + + -- final HP value is clamped to <= U16_MAX before difference calculation + expected_diff = 65535 - 22 + player:set_hp(1000000) + -- and actual final HP value is clamped to <= hp_max + assert(player:get_hp() == 30) + + expected_diff = nil + player:set_properties({hp_max = old_hp_max}) + player:set_hp(old_hp) + core.close_formspec(player:get_player_name(), "") -- hide death screen +end +unittests.register("test_hp_difference", run_hp_difference_tests, {player=true}) + -- -- Player meta -- From 6608057971f7ce247b2a7e10f0b88fd0e9bc9794 Mon Sep 17 00:00:00 2001 From: grorp Date: Sat, 31 Aug 2024 20:46:14 +0200 Subject: [PATCH 053/200] Fix uninitialized SkyboxParams::fog_color --- src/skyparams.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/skyparams.h b/src/skyparams.h index a4d0fadac..2ff918f36 100644 --- a/src/skyparams.h +++ b/src/skyparams.h @@ -46,7 +46,7 @@ struct SkyboxParams float body_orbit_tilt { INVALID_SKYBOX_TILT }; s16 fog_distance { -1 }; float fog_start { -1.0f }; - video::SColor fog_color; // override, only used if alpha > 0 + video::SColor fog_color { 0 }; // override, only used if alpha > 0 }; struct SunParams From 1b8b84bee828db44a9c3e5d48e741ee618fff082 Mon Sep 17 00:00:00 2001 From: red-001 Date: Sat, 31 Aug 2024 16:44:12 +0100 Subject: [PATCH 054/200] connection: Remove unused timeout feature Was only used for a unit test and incorrectly at that. --- src/client/client.cpp | 2 -- src/network/connection.h | 2 -- src/network/mtp/impl.cpp | 7 ----- src/network/mtp/impl.h | 3 -- src/network/networkexceptions.h | 6 ---- src/server.cpp | 1 - src/unittest/test_connection.cpp | 53 +++++++++++++++----------------- 7 files changed, 24 insertions(+), 50 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index d15c9608a..0c222b111 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -398,8 +398,6 @@ void Client::connect(const Address &address, const std::string &address_name, address.print(infostream); infostream << std::endl; - // Since we use TryReceive() a timeout here would be ineffective anyway - m_con->SetTimeoutMs(0); m_con->Connect(address); initLocalMapSaving(address, m_address_name, is_local_server); diff --git a/src/network/connection.h b/src/network/connection.h index a3665566e..bec6f98f0 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -48,7 +48,6 @@ class IConnection public: virtual ~IConnection() = default; - virtual void SetTimeoutMs(u32 timeout) = 0; virtual void Serve(Address bind_addr) = 0; virtual void Connect(Address address) = 0; virtual bool Connected() = 0; @@ -56,7 +55,6 @@ public: virtual void DisconnectPeer(session_t peer_id) = 0; virtual bool ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms) = 0; - virtual void Receive(NetworkPacket *pkt) = 0; bool TryReceive(NetworkPacket *pkt) { return ReceiveTimeoutMs(pkt, 0); } diff --git a/src/network/mtp/impl.cpp b/src/network/mtp/impl.cpp index 2d4a99119..00535945e 100644 --- a/src/network/mtp/impl.cpp +++ b/src/network/mtp/impl.cpp @@ -1486,13 +1486,6 @@ bool Connection::ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms) return false; } -void Connection::Receive(NetworkPacket *pkt) -{ - bool any = ReceiveTimeoutMs(pkt, m_bc_receive_timeout); - if (!any) - throw NoIncomingDataException("No incoming data"); -} - void Connection::Send(session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable) { diff --git a/src/network/mtp/impl.h b/src/network/mtp/impl.h index 0232ebc0d..cc1d4c2ed 100644 --- a/src/network/mtp/impl.h +++ b/src/network/mtp/impl.h @@ -249,13 +249,11 @@ public: void putCommand(ConnectionCommandPtr c); - void SetTimeoutMs(u32 timeout) { m_bc_receive_timeout = timeout; } void Serve(Address bind_addr); void Connect(Address address); bool Connected(); void Disconnect(); bool ReceiveTimeoutMs(NetworkPacket *pkt, u32 timeout_ms); - void Receive(NetworkPacket *pkt); void Send(session_t peer_id, u8 channelnum, NetworkPacket *pkt, bool reliable); session_t GetPeerID() const { return m_peer_id; } Address GetPeerAddress(session_t peer_id); @@ -317,7 +315,6 @@ private: // Backwards compatibility PeerHandler *m_bc_peerhandler; - u32 m_bc_receive_timeout = 0; bool m_shutting_down = false; }; diff --git a/src/network/networkexceptions.h b/src/network/networkexceptions.h index 2e9c2a6e8..1810106e5 100644 --- a/src/network/networkexceptions.h +++ b/src/network/networkexceptions.h @@ -51,12 +51,6 @@ public: InvalidIncomingDataException(const char *s) : BaseException(s) {} }; -class NoIncomingDataException : public BaseException -{ -public: - NoIncomingDataException(const char *s) : BaseException(s) {} -}; - } class SocketException : public BaseException diff --git a/src/server.cpp b/src/server.cpp index a78244e99..bad0e70d2 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -551,7 +551,6 @@ void Server::start() m_thread->stop(); // Initialize connection - m_con->SetTimeoutMs(30); m_con->Serve(m_bind_addr); // Start thread diff --git a/src/unittest/test_connection.cpp b/src/unittest/test_connection.cpp index 3c0043389..b7d751dc3 100644 --- a/src/unittest/test_connection.cpp +++ b/src/unittest/test_connection.cpp @@ -160,6 +160,9 @@ void TestConnection::testHelpers() void TestConnection::testConnectSendReceive() { + + constexpr u32 timeout_ms = 100; + /* Test some real connections @@ -210,13 +213,11 @@ void TestConnection::testConnectSendReceive() // Client should not have added client yet UASSERT(hand_client.count == 0); - try { - NetworkPacket pkt; - infostream << "** running client.Receive()" << std::endl; - client.Receive(&pkt); + NetworkPacket pkt; + infostream << "** running client.Receive()" << std::endl; + if (client.ReceiveTimeoutMs(&pkt, timeout_ms)) { infostream << "** Client received: peer_id=" << pkt.getPeerId() << ", size=" << pkt.getSize() << std::endl; - } catch (con::NoIncomingDataException &e) { } // Client should have added server now @@ -227,14 +228,14 @@ void TestConnection::testConnectSendReceive() sleep_ms(100); - try { - NetworkPacket pkt; - infostream << "** running server.Receive()" << std::endl; - server.Receive(&pkt); + NetworkPacket pkt1; + infostream << "** running server.Receive()" << std::endl; + if (server.ReceiveTimeoutMs(&pkt, timeout_ms)) { infostream << "** Server received: peer_id=" << pkt.getPeerId() - << ", size=" << pkt.getSize() - << std::endl; - } catch (con::NoIncomingDataException &e) { + << ", size=" << pkt.getSize() + << std::endl; + } + else { // No actual data received, but the client has // probably been connected } @@ -249,27 +250,23 @@ void TestConnection::testConnectSendReceive() //sleep_ms(50); while (client.Connected() == false) { - try { - NetworkPacket pkt; - infostream << "** running client.Receive()" << std::endl; - client.Receive(&pkt); + NetworkPacket pkt; + infostream << "** running client.Receive()" << std::endl; + if (client.TryReceive(&pkt)) { infostream << "** Client received: peer_id=" << pkt.getPeerId() << ", size=" << pkt.getSize() << std::endl; - } catch (con::NoIncomingDataException &e) { } sleep_ms(50); } sleep_ms(50); - try { - NetworkPacket pkt; - infostream << "** running server.Receive()" << std::endl; - server.Receive(&pkt); + NetworkPacket pkt2; + infostream << "** running server.Receive()" << std::endl; + if (server.ReceiveTimeoutMs(&pkt, timeout_ms)) { infostream << "** Server received: peer_id=" << pkt.getPeerId() - << ", size=" << pkt.getSize() - << std::endl; - } catch (con::NoIncomingDataException &e) { + << ", size=" << pkt.getSize() + << std::endl; } /* @@ -288,7 +285,7 @@ void TestConnection::testConnectSendReceive() NetworkPacket recvpacket; infostream << "** running server.Receive()" << std::endl; - server.Receive(&recvpacket); + UASSERT(server.ReceiveTimeoutMs(&recvpacket, timeout_ms)); infostream << "** Server received: peer_id=" << pkt.getPeerId() << ", size=" << pkt.getSize() << ", data=" << (const char*)pkt.getU8Ptr(0) @@ -338,14 +335,12 @@ void TestConnection::testConnectSendReceive() for (;;) { if (porting::getTimeMs() - timems0 > 5000 || received) break; - try { - NetworkPacket pkt; - client.Receive(&pkt); + NetworkPacket pkt; + if (client.ReceiveTimeoutMs(&pkt, timeout_ms)) { size = pkt.getSize(); peer_id = pkt.getPeerId(); recvdata = pkt.oldForgePacket(); received = true; - } catch (con::NoIncomingDataException &e) { } sleep_ms(10); } From 48e65ac846479c2712496851e635ef65b8e532b1 Mon Sep 17 00:00:00 2001 From: red-001 Date: Sat, 31 Aug 2024 16:42:37 +0100 Subject: [PATCH 055/200] Don't attempt to process packets when there are none Under certain unlikely circumstances the main server loop could attempt to process packets even when the connection didn't return one. This would result in the default empty packet being processed resulting in spurious warnings about a missing client. --- src/server.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server.cpp b/src/server.cpp index bad0e70d2..fe831d0f7 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1076,6 +1076,8 @@ void Server::Receive(float timeout) // and a faster server-step is better than busy waiting. if (remaining_time_us() < 1000.0f) break; + else + continue; } peer_id = pkt.getPeerId(); From 43363ee0665840218307c37b931d643d54dfbf85 Mon Sep 17 00:00:00 2001 From: red-001 Date: Sat, 31 Aug 2024 19:47:29 +0100 Subject: [PATCH 056/200] Disable CRT security warnings in MSVC (#15077) MSVC by default warns if Annex-K style secure functions with additional parameter validation are not used. For better or worse, afaik other major compilers don't implement it, so it's not a very useful warning for a cross-platform project. --- irr/src/CMakeLists.txt | 4 ++++ lib/lua/CMakeLists.txt | 4 ++++ src/CMakeLists.txt | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 2bc935b9f..161a0c060 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -83,6 +83,10 @@ if(LINUX_PLATFORM) add_definitions(-D_IRR_LINUX_PLATFORM_) endif() +if(MSVC) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif() + if(USE_SDL2) set(DEVICE "SDL") elseif(DEVICE STREQUAL "SDL") diff --git a/lib/lua/CMakeLists.txt b/lib/lua/CMakeLists.txt index 2de4840cb..869a3c320 100644 --- a/lib/lua/CMakeLists.txt +++ b/lib/lua/CMakeLists.txt @@ -22,6 +22,10 @@ else() set(DEFAULT_ANSI TRUE) endif() +if(MSVC) + set(COMMON_CFLAGS "${COMMON_CFLAGS} -D_CRT_SECURE_NO_WARNINGS") +endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(COMMON_LDFLAGS "${COMMON_LDFLAGS} -Wl,-E -lm") set(DEFAULT_DLOPEN ON) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 955bf05f2..0ea8a40a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -781,7 +781,7 @@ endif() if(MSVC) # Visual Studio - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D _WIN32_WINNT=0x0601 /D WIN32_LEAN_AND_MEAN") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D _WIN32_WINNT=0x0601 /D WIN32_LEAN_AND_MEAN /D _CRT_SECURE_NO_WARNINGS") # EHa enables SEH exceptions (used for catching segfaults) set(CMAKE_CXX_FLAGS_RELEASE "/EHa /Ox /MD /GS- /Zi /fp:fast /D NDEBUG /D _HAS_ITERATOR_DEBUGGING=0") if(CMAKE_SIZEOF_VOID_P EQUAL 4) From 7afa78ec82243a118d8954a75587486d54a6b378 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 19 Aug 2024 22:05:59 +0200 Subject: [PATCH 057/200] Remove obsolete client connection init workaround m_connection_reinit_timer has a head-start of 0.1s and this code only took effect for the very first game session so it was broken anyway. --- src/client/client.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index 0c222b111..d07a8eb4a 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -436,20 +436,14 @@ void Client::step(float dtime) } } - // UGLY hack to fix 2 second startup delay caused by non existent - // server client startup synchronization in local server or singleplayer mode - static bool initial_step = true; - if (initial_step) { - initial_step = false; - } - else if(m_state == LC_Created) { + if (m_state == LC_Created) { float &counter = m_connection_reinit_timer; counter -= dtime; - if(counter <= 0.0) { - counter = 2.0; + if (counter <= 0) { + counter = 1.5f; LocalPlayer *myplayer = m_env.getLocalPlayer(); - FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment."); + FATAL_ERROR_IF(!myplayer, "Local player not found in environment"); sendInit(myplayer->getName()); } From 1380bf9b88521ccd57d510abe51c7eef68919b92 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 21 Aug 2024 22:13:03 +0200 Subject: [PATCH 058/200] Fix ordering issue with new server peers --- src/client/client.cpp | 2 ++ src/network/peerhandler.h | 21 ++------------------ src/server.cpp | 41 +++++---------------------------------- src/server.h | 7 ------- 4 files changed, 9 insertions(+), 62 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index d07a8eb4a..9c12be8f2 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -436,6 +436,8 @@ void Client::step(float dtime) } } + // The issue that made this workaround necessary was fixed in August 2024, but + // it's not like we can remove this code - ever. if (m_state == LC_Created) { float &counter = m_connection_reinit_timer; counter -= dtime; diff --git a/src/network/peerhandler.h b/src/network/peerhandler.h index 1e00249ca..64e2c85fb 100644 --- a/src/network/peerhandler.h +++ b/src/network/peerhandler.h @@ -30,9 +30,10 @@ class PeerHandler { public: PeerHandler() = default; - virtual ~PeerHandler() = default; + // Note: all functions are called from within a Receive() call on the same thread. + /* This is called after the Peer has been inserted into the Connection's peer container. @@ -46,22 +47,4 @@ public: virtual void deletingPeer(IPeer *peer, bool timeout) = 0; }; -enum PeerChangeType : u8 -{ - PEER_ADDED, - PEER_REMOVED -}; - -struct PeerChange -{ - PeerChange(PeerChangeType t, session_t _peer_id, bool _timeout) : - type(t), peer_id(_peer_id), timeout(_timeout) - { - } - PeerChange() = delete; - - PeerChangeType type; - session_t peer_id; - bool timeout; -}; } diff --git a/src/server.cpp b/src/server.cpp index fe831d0f7..609b7188b 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -623,8 +623,6 @@ void Server::AsyncRunStep(float dtime, bool initial_step) */ m_uptime_counter->increment(dtime); - handlePeerChanges(); - /* Update time of day and overall game time */ @@ -1257,19 +1255,18 @@ void Server::onMapEditEvent(const MapEditEvent &event) void Server::peerAdded(con::IPeer *peer) { - verbosestream<<"Server::peerAdded(): peer->id=" - <id<id=" - <id<<", timeout="< m_peer_change_queue; - std::unordered_map m_formspec_state_data; /* From 8972c80d7d5c5abdd31a0ab7caec7076769e1adb Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 18 Aug 2024 22:06:48 +0200 Subject: [PATCH 059/200] Warn if max_packets_per_iteration reduced --- builtin/settingtypes.txt | 5 ++--- src/network/mtp/threads.cpp | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index ccafde036..dd193a0c9 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2024,9 +2024,8 @@ max_simultaneous_block_sends_per_client (Maximum simultaneous block sends per cl # This determines how long they are slowed down after placing or removing a node. full_block_send_enable_min_time_from_building (Delay in sending blocks after building) float 2.0 0.0 -# Maximum number of packets sent per send step, if you have a slow connection -# try reducing it, but don't reduce it to a number below double of targeted -# client number. +# Maximum number of packets sent per send step in the low-level networking code. +# You generally don't need to change this, however busy servers may benefit from a higher number. max_packets_per_iteration (Max. packets per iteration) int 1024 1 65535 # Compression level to use when sending mapblocks to the client. diff --git a/src/network/mtp/threads.cpp b/src/network/mtp/threads.cpp index c7c728c9d..d1a1e2a34 100644 --- a/src/network/mtp/threads.cpp +++ b/src/network/mtp/threads.cpp @@ -59,14 +59,24 @@ static inline u8 readChannel(const u8 *packetdata) /* Connection Threads */ /******************************************************************************/ +#define MPPI_SETTING "max_packets_per_iteration" + ConnectionSendThread::ConnectionSendThread(unsigned int max_packet_size, float timeout) : Thread("ConnectionSend"), m_max_packet_size(max_packet_size), m_timeout(timeout), - m_max_data_packets_per_iteration(g_settings->getU16("max_packets_per_iteration")) + m_max_data_packets_per_iteration(g_settings->getU16(MPPI_SETTING)) { - SANITY_CHECK(m_max_data_packets_per_iteration > 1); + auto &mppi = m_max_data_packets_per_iteration; + mppi = MYMAX(mppi, 1); + + const auto mppi_default = Settings::getLayer(SL_DEFAULTS)->getU16(MPPI_SETTING); + if (mppi < mppi_default) { + warningstream << "You are running the network code with a non-default " + "configuration (" MPPI_SETTING "=" << mppi << "). " + "This is not recommended in production." << std::endl; + } } void *ConnectionSendThread::run() @@ -769,7 +779,7 @@ void ConnectionSendThread::sendPackets(float dtime, u32 peer_packet_quota) } } - if (peer_packet_quota > 0) { + if (peer_packet_quota > 0 && !stopRequested()) { for (session_t peerId : peerIds) { PeerHelper peer = m_connection->getPeerNoEx(peerId); if (!peer) From ac11a14509067604df23834d993a6251962db043 Mon Sep 17 00:00:00 2001 From: JosiahWI <41302989+JosiahWI@users.noreply.github.com> Date: Mon, 2 Sep 2024 07:50:30 -0500 Subject: [PATCH 060/200] Add static glTF support (#14557) Co-authored-by: Lars Mueller Co-authored-by: jordan4ibanez Co-authored-by: sfan5 Co-authored-by: SmallJoker --- CMakeLists.txt | 2 + doc/lua_api.md | 39 +- games/devtest/mods/gltf/LICENSE.md | 14 + games/devtest/mods/gltf/init.lua | 51 + games/devtest/mods/gltf/invalid/empty.gltf | 0 .../invalid/invalid_bufferview_bounds.gltf | 1 + .../mods/gltf/invalid/json_missing_brace.gltf | 1 + games/devtest/mods/gltf/mod.conf | 2 + .../mods/gltf/models/gltf_blender_cube.gltf | 1 + .../gltf_blender_cube_matrix_transform.gltf | 1 + .../gltf/models/gltf_blender_cube_scaled.gltf | 1 + games/devtest/mods/gltf/models/gltf_frog.gltf | 1 + .../gltf/models/gltf_minimal_triangle.gltf | 1 + .../models/gltf_simple_sparse_accessor.gltf | 1 + .../mods/gltf/models/gltf_snow_man.gltf | 1 + .../devtest/mods/gltf/models/gltf_spider.gltf | 1 + .../gltf_triangle_with_vertex_stride.gltf | 1 + .../models/gltf_triangle_without_indices.gltf | 1 + .../devtest/mods/gltf/textures/gltf_cube.png | Bin 0 -> 203 bytes .../devtest/mods/gltf/textures/gltf_frog.png | Bin 0 -> 272 bytes .../mods/gltf/textures/gltf_snow_man.png | Bin 0 -> 205 bytes .../mods/gltf/textures/gltf_spider.png | Bin 0 -> 10957 bytes irr/include/IMesh.h | 11 + irr/include/ISkinnedMesh.h | 3 + irr/include/SMesh.h | 14 + irr/include/SSkinMeshBuffer.h | 11 +- irr/include/irrArray.h | 4 + irr/include/irrString.h | 13 +- irr/src/CGLTFMeshFileLoader.cpp | 695 +++++++++ irr/src/CGLTFMeshFileLoader.h | 147 ++ irr/src/CMakeLists.txt | 10 +- irr/src/CSceneManager.cpp | 2 + irr/src/CSkinnedMesh.cpp | 17 + irr/src/CSkinnedMesh.h | 9 + lib/tiniergltf/.gitignore | 6 + lib/tiniergltf/CMakeLists.txt | 22 + lib/tiniergltf/Readme.md | 39 + lib/tiniergltf/tiniergltf.hpp | 1357 +++++++++++++++++ src/client/client.cpp | 2 +- src/client/content_cao.cpp | 21 +- src/client/content_mapblock.cpp | 5 +- src/client/mesh.cpp | 2 +- src/gui/guiFormSpecMenu.cpp | 9 +- src/server.cpp | 2 +- src/unittest/CMakeLists.txt | 2 +- src/unittest/test_irr_gltf_mesh_loader.cpp | 366 +++++ src/unittest/test_servermodmanager.cpp | 2 +- 47 files changed, 2863 insertions(+), 28 deletions(-) create mode 100644 games/devtest/mods/gltf/LICENSE.md create mode 100644 games/devtest/mods/gltf/init.lua create mode 100644 games/devtest/mods/gltf/invalid/empty.gltf create mode 100644 games/devtest/mods/gltf/invalid/invalid_bufferview_bounds.gltf create mode 100644 games/devtest/mods/gltf/invalid/json_missing_brace.gltf create mode 100644 games/devtest/mods/gltf/mod.conf create mode 100644 games/devtest/mods/gltf/models/gltf_blender_cube.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_blender_cube_matrix_transform.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_blender_cube_scaled.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_frog.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_minimal_triangle.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_simple_sparse_accessor.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_snow_man.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_spider.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_triangle_with_vertex_stride.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_triangle_without_indices.gltf create mode 100644 games/devtest/mods/gltf/textures/gltf_cube.png create mode 100644 games/devtest/mods/gltf/textures/gltf_frog.png create mode 100644 games/devtest/mods/gltf/textures/gltf_snow_man.png create mode 100644 games/devtest/mods/gltf/textures/gltf_spider.png create mode 100644 irr/src/CGLTFMeshFileLoader.cpp create mode 100644 irr/src/CGLTFMeshFileLoader.h create mode 100644 lib/tiniergltf/.gitignore create mode 100644 lib/tiniergltf/CMakeLists.txt create mode 100644 lib/tiniergltf/Readme.md create mode 100644 lib/tiniergltf/tiniergltf.hpp create mode 100644 src/unittest/test_irr_gltf_mesh_loader.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6623fa828..54a830089 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -283,6 +283,8 @@ if(BUILD_UNITTESTS OR BUILD_BENCHMARKS) add_subdirectory(lib/catch2) endif() +add_subdirectory(lib/tiniergltf) + # Subdirectories # Be sure to add all relevant definitions above this add_subdirectory(src) diff --git a/doc/lua_api.md b/doc/lua_api.md index e6a351918..6989f3483 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -274,7 +274,7 @@ Accepted formats are: images: .png, .jpg, .tga, (deprecated:) .bmp sounds: .ogg vorbis - models: .x, .b3d, .obj + models: .x, .b3d, .obj, .gltf (Minetest 5.10 or newer) Other formats won't be sent to the client (e.g. you can store .blend files in a folder for convenience, without the risk that such files are transferred) @@ -291,6 +291,43 @@ in one of its parents, the parent's file is used. Although it is discouraged, a mod can overwrite a media file of any mod that it depends on by supplying a file with an equal name. +Only a subset of model file format features is supported: + +Simple textured meshes (with multiple textures), optionally with normals. +The .x and .b3d formats additionally support skeletal animation. + +#### glTF + +The glTF model file format for now only serves as a +more modern alternative to the other static model file formats; +it unlocks no special rendering features. + +This means that many glTF features are not supported *yet*, including: + +* Animation +* Cameras +* Materials + * Only base color textures are supported + * Backface culling is overridden + * Double-sided materials don't work +* Alternative means of supplying data + * Embedded images + * References to files via URIs + +Textures are supplied solely via the same means as for the other model file formats: +The `textures` object property, the `tiles` node definition field and +the list of textures used in the `model[]` formspec element. + +The order in which textures are to be supplied +is that in which they appear in the `textures` array in the glTF file. + +Do not rely on glTF features not being supported; they may be supported in the future. +The backwards compatibility guarantee does not extend to ignoring unsupported features. + +For example, if your model used an emissive material, +you should expect that a future version of Minetest may respect this, +and thus cause your model to render differently there. + Naming conventions ------------------ diff --git a/games/devtest/mods/gltf/LICENSE.md b/games/devtest/mods/gltf/LICENSE.md new file mode 100644 index 000000000..b0ae5fef5 --- /dev/null +++ b/games/devtest/mods/gltf/LICENSE.md @@ -0,0 +1,14 @@ +glTF test model (and corresponding texture) licenses: + +* Spider (`gltf_spider.gltf`, `gltf_spider.png`): + * By [archfan7411](https://github.com/archfan7411) + * Licensed under CC0, public domain "wherever public domain carries fewer rights or legal protections" +* Frog (`gltf_frog.gltf`, `gltf_frog.png`): + * By [Susybaka1234](https://sketchfab.com/3d-models/african-clawed-frog-v2-c81152c93948480c931c280d18957358) + * Licensed under CC-BY 4.0 +* Snow Man (`gltf_snow_man.gltf`, `gltf_snow_man.png`): + * By [jordan4ibanez](https://github.com/jordan4ibanez) + * Licensed under CC0 +* Minimal triangle, triangle without indices (`gltf_minimal_triangle.gltf`, `gltf_triangle_without_indices.gltf`) + * From [the glTF sample model collection](https://github.com/KhronosGroup/glTF-Sample-Models) + * Licensed under CC0 / public domain diff --git a/games/devtest/mods/gltf/init.lua b/games/devtest/mods/gltf/init.lua new file mode 100644 index 000000000..b5c2032bc --- /dev/null +++ b/games/devtest/mods/gltf/init.lua @@ -0,0 +1,51 @@ +local function register_entity(name, textures, backface_culling) + minetest.register_entity("gltf:" .. name, { + initial_properties = { + visual = "mesh", + mesh = "gltf_" .. name .. ".gltf", + textures = textures, + backface_culling = backface_culling, + }, + }) +end + +-- These do not have texture coordinates; they simple render as black surfaces. +register_entity("minimal_triangle", {}, false) +register_entity("triangle_with_vertex_stride", {}, false) +register_entity("triangle_without_indices", {}, false) +do + local cube_textures = {"gltf_cube.png"} + register_entity("blender_cube", cube_textures) + register_entity("blender_cube_scaled", cube_textures) + register_entity("blender_cube_matrix_transform", cube_textures) +end +register_entity("snow_man", {"gltf_snow_man.png"}) +register_entity("spider", {"gltf_spider.png"}) +-- Note: Model has an animation, but we can use it as a static test nevertheless +-- The claws rendering incorrectly from one side is expected behavior: +-- They use an unsupported double-sided material. +register_entity("frog", {"gltf_frog.png"}, false) + +minetest.register_node("gltf:frog", { + description = "glTF frog, but it's a node", + tiles = {{name = "gltf_frog.png", backface_culling = false}}, + drawtype = "mesh", + mesh = "gltf_frog.gltf", +}) + +minetest.register_chatcommand("show_model", { + params = " [textures]", + description = "Show a model (defaults to gltf models, for example '/show_model frog').", + func = function(name, param) + local model, textures = param:match"^(.-)%s+(.+)$" + if not model then + model = "gltf_" .. param .. ".gltf" + textures = "gltf_" .. param .. ".png" + end + minetest.show_formspec(name, "gltf:model", table.concat{ + "formspec_version[7]", + "size[10,10]", + "model[0,0;10,10;model;", model, ";", textures, ";0,0;true;true;0,0;0]", + }) + end, +}) diff --git a/games/devtest/mods/gltf/invalid/empty.gltf b/games/devtest/mods/gltf/invalid/empty.gltf new file mode 100644 index 000000000..e69de29bb diff --git a/games/devtest/mods/gltf/invalid/invalid_bufferview_bounds.gltf b/games/devtest/mods/gltf/invalid/invalid_bufferview_bounds.gltf new file mode 100644 index 000000000..2182861c6 --- /dev/null +++ b/games/devtest/mods/gltf/invalid/invalid_bufferview_bounds.gltf @@ -0,0 +1 @@ +{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"buffers":[{"uri":"data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA","byteLength":36}],"bufferViews":[{"buffer":0,"byteOffset":1,"byteLength":36,"target":34962}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5126,"count":3,"type":"VEC3","max":[1,1,0],"min":[0,0,0]}],"asset":{"version":"2.0"}} diff --git a/games/devtest/mods/gltf/invalid/json_missing_brace.gltf b/games/devtest/mods/gltf/invalid/json_missing_brace.gltf new file mode 100644 index 000000000..98232c64f --- /dev/null +++ b/games/devtest/mods/gltf/invalid/json_missing_brace.gltf @@ -0,0 +1 @@ +{ diff --git a/games/devtest/mods/gltf/mod.conf b/games/devtest/mods/gltf/mod.conf new file mode 100644 index 000000000..3ec50d2ef --- /dev/null +++ b/games/devtest/mods/gltf/mod.conf @@ -0,0 +1,2 @@ +name = gltf +description = Hosts gltf test models, both for the C++ unit tests and for in-game viewing diff --git a/games/devtest/mods/gltf/models/gltf_blender_cube.gltf b/games/devtest/mods/gltf/models/gltf_blender_cube.gltf new file mode 100644 index 000000000..041b4a1fc --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_blender_cube.gltf @@ -0,0 +1 @@ +{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[0]}],"nodes":[{"mesh":0,"name":"Cube","scale":[10,10,10]}],"meshes":[{"name":"Cube.004","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3}]}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"type":"SCALAR"}],"bufferViews":[{"buffer":0,"byteLength":288,"byteOffset":0},{"buffer":0,"byteLength":288,"byteOffset":288},{"buffer":0,"byteLength":192,"byteOffset":576},{"buffer":0,"byteLength":72,"byteOffset":768}],"buffers":[{"byteLength":840,"uri":"data:application/octet-stream;base64,AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAADAPgAAgD8AAAA+AACAPgAAwD4AAAAAAAAgPwAAgD8AACA/AAAAAAAAYD8AAIA+AADAPgAAQD8AAAA+AAAAPwAAwD4AAEA/AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AAADAAkAAAAJAAYACAAKABUACAAVABMAFAAXABEAFAARAA4ADQAPAAQADQAEAAIABwASAAwABwAMAAEAFgALAAUAFgAFABAA"}]} diff --git a/games/devtest/mods/gltf/models/gltf_blender_cube_matrix_transform.gltf b/games/devtest/mods/gltf/models/gltf_blender_cube_matrix_transform.gltf new file mode 100644 index 000000000..50235ceae --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_blender_cube_matrix_transform.gltf @@ -0,0 +1 @@ +{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[0]}],"nodes":[{"mesh":0,"name":"Cube","matrix":[1,0,0,0,0,2,0,0,0,0,3,0,4,5,6,1]}],"meshes":[{"name":"Cube.004","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3}]}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"type":"SCALAR"}],"bufferViews":[{"buffer":0,"byteLength":288,"byteOffset":0},{"buffer":0,"byteLength":288,"byteOffset":288},{"buffer":0,"byteLength":192,"byteOffset":576},{"buffer":0,"byteLength":72,"byteOffset":768}],"buffers":[{"byteLength":840,"uri":"data:application/octet-stream;base64,AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAADAPgAAgD8AAAA+AACAPgAAwD4AAAAAAAAgPwAAgD8AACA/AAAAAAAAYD8AAIA+AADAPgAAQD8AAAA+AAAAPwAAwD4AAEA/AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AAADAAkAAAAJAAYACAAKABUACAAVABMAFAAXABEAFAARAA4ADQAPAAQADQAEAAIABwASAAwABwAMAAEAFgALAAUAFgAFABAA"}]} diff --git a/games/devtest/mods/gltf/models/gltf_blender_cube_scaled.gltf b/games/devtest/mods/gltf/models/gltf_blender_cube_scaled.gltf new file mode 100644 index 000000000..3b626b37e --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_blender_cube_scaled.gltf @@ -0,0 +1 @@ +{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[0]}],"nodes":[{"mesh":0,"name":"Cube","scale":[150,1,21.5]}],"meshes":[{"name":"Cube.004","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3}]}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"type":"SCALAR"}],"bufferViews":[{"buffer":0,"byteLength":288,"byteOffset":0},{"buffer":0,"byteLength":288,"byteOffset":288},{"buffer":0,"byteLength":192,"byteOffset":576},{"buffer":0,"byteLength":72,"byteOffset":768}],"buffers":[{"byteLength":840,"uri":"data:application/octet-stream;base64,AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAADAPgAAgD8AAAA+AACAPgAAwD4AAAAAAAAgPwAAgD8AACA/AAAAAAAAYD8AAIA+AADAPgAAQD8AAAA+AAAAPwAAwD4AAEA/AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AAADAAkAAAAJAAYACAAKABUACAAVABMAFAAXABEAFAARAA4ADQAPAAQADQAEAAIABwASAAwABwAMAAEAFgALAAUAFgAFABAA"}]} diff --git a/games/devtest/mods/gltf/models/gltf_frog.gltf b/games/devtest/mods/gltf/models/gltf_frog.gltf new file mode 100644 index 000000000..201604fd3 --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_frog.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"Blockbench 4.9.4 glTF exporter"},"scenes":[{"nodes":[20],"name":"blockbench_export"}],"scene":0,"nodes":[{"name":"cube","mesh":0},{"name":"cube","mesh":1},{"name":"cube","mesh":2},{"name":"body","children":[0,1,2]},{"translation":[0,0,-0.0625],"name":"cube","mesh":3},{"translation":[0.03125,0,-0.3125],"name":"cube","mesh":4},{"rotation":[0,-0.19509032201612825,0,0.9807852804032304],"translation":[0.01812248876854733,-0.0625,-0.25194388507103505],"name":"cube","mesh":5},{"translation":[0.0625,0,0.3125],"name":"leftleg","children":[4,5,6]},{"translation":[0.0625,0,-0.3125],"name":"cube","mesh":6},{"translation":[-0.03125,0,-0.3125],"name":"cube","mesh":7},{"rotation":[0,0.19509032201612825,0,0.9807852804032304],"translation":[-0.01812248876854733,-0.0625,-0.25194388507103505],"name":"cube","mesh":8},{"translation":[-0.0625,0,0.3125],"name":"rightleg","children":[8,9,10]},{"translation":[-0.125,-0.0625,0.125],"name":"cube","mesh":9},{"rotation":[0,0.5372996083468239,0,0.8433914458128857],"translation":[0.10431178959951112,-0.0625,0.2349474087973531],"name":"cube","mesh":10},{"rotation":[0,0.5372996083468239,0,0.8433914458128857],"translation":[0.10431178959951112,-0.0625,0.2349474087973531],"name":"cube","mesh":11},{"translation":[0.125,0.0625,-0.125],"name":"leftarm","children":[12,13,14]},{"translation":[0.125,-0.0625,0.125],"name":"cube","mesh":12},{"rotation":[0,-0.5372996083468239,0,0.8433914458128857],"translation":[-0.10431178959951112,-0.0625,0.2349474087973531],"name":"cube","mesh":13},{"rotation":[0,-0.5372996083468239,0,0.8433914458128857],"translation":[-0.10431178959951112,-0.0625,0.2349474087973531],"name":"cube","mesh":14},{"translation":[-0.125,0.0625,-0.125],"name":"rightarm","children":[16,17,18]},{"children":[3,7,11,15,19]}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":288,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":576,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":768,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":840,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1128,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1416,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":1608,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":1680,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1968,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":2256,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":2448,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":2520,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":2808,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":3096,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":3288,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":3360,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":3648,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":3936,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":4128,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":4200,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":4488,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":4776,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":4968,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":5040,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":5328,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":5616,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":5808,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":5880,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":6168,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":6456,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":6648,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":6720,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":7008,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":7296,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":7488,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":7560,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":7848,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":8136,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":8328,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":8400,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":8688,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":8976,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":9168,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":9240,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":9528,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":9816,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":10008,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":10080,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":10368,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":10656,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":10848,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":10920,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":11208,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":11496,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":11688,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":11760,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":12048,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":12336,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":12528,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":12600,"byteLength":12},{"buffer":0,"byteOffset":12612,"byteLength":48},{"buffer":0,"byteOffset":12660,"byteLength":12},{"buffer":0,"byteOffset":12672,"byteLength":48},{"buffer":0,"byteOffset":12720,"byteLength":12},{"buffer":0,"byteOffset":12732,"byteLength":48},{"buffer":0,"byteOffset":12780,"byteLength":12},{"buffer":0,"byteOffset":12792,"byteLength":48},{"buffer":0,"byteOffset":12840,"byteLength":12},{"buffer":0,"byteOffset":12852,"byteLength":48},{"buffer":0,"byteOffset":12900,"byteLength":12},{"buffer":0,"byteOffset":12912,"byteLength":48},{"buffer":0,"byteOffset":12960,"byteLength":12},{"buffer":0,"byteOffset":12972,"byteLength":48},{"buffer":0,"byteOffset":13020,"byteLength":12},{"buffer":0,"byteOffset":13032,"byteLength":48},{"buffer":0,"byteOffset":13080,"byteLength":12},{"buffer":0,"byteOffset":13092,"byteLength":48},{"buffer":0,"byteOffset":13140,"byteLength":4},{"buffer":0,"byteOffset":13144,"byteLength":16},{"buffer":0,"byteOffset":13160,"byteLength":4},{"buffer":0,"byteOffset":13164,"byteLength":16}],"buffers":[{"byteLength":13180,"uri":"data:application/octet-stream;base64,AAAgPgAAAD4AAIA+AAAgPgAAAD4AAIC9AAAgPgAAAAAAAIA+AAAgPgAAAAAAAIC9AAAgvgAAAD4AAIC9AAAgvgAAAD4AAIA+AAAgvgAAAAAAAIC9AAAgvgAAAAAAAIA+AAAgvgAAAD4AAIC9AAAgPgAAAD4AAIC9AAAgvgAAAD4AAIA+AAAgPgAAAD4AAIA+AAAgvgAAAAAAAIA+AAAgPgAAAAAAAIA+AAAgvgAAAAAAAIC9AAAgPgAAAAAAAIC9AAAgvgAAAD4AAIA+AAAgPgAAAD4AAIA+AAAgvgAAAAAAAIA+AAAgPgAAAAAAAIA+AAAgPgAAAD4AAIC9AAAgvgAAAD4AAIC9AAAgPgAAAAAAAIC9AAAgvgAAAAAAAIC9AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgCAID4AgB8+AIAgPgAAADoAgF8+AIAfPgCAXz4AQKA+AIAgPgDA7z4AgCA+AECgPgCAXz4AwO8+AIBfPgDAnz4AgB8+AIAgPgCAHz4AwJ8+AAAAOgCAID4AAAA6AMDvPgAAADoAQKA+AAAAOgDA7z4AgB8+AECgPgCAHz4AQPA+AIAgPgDgHz8AgCA+AEDwPgCAXz4A4B8/AIBfPgCAID4AgCA+AMCfPgCAID4AgCA+AIBfPgDAnz4AgF8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAPgAAAD4AAIC9AAAAPgAAAD4AAKC+AAAAPgAAAAAAAIC9AAAAPgAAAAAAAKC+AAAAvgAAAD4AAKC+AAAAvgAAAD4AAIC9AAAAvgAAAAAAAKC+AAAAvgAAAAAAAIC9AAAAvgAAAD4AAKC+AAAAPgAAAD4AAKC+AAAAvgAAAD4AAIC9AAAAPgAAAD4AAIC9AAAAvgAAAAAAAIC9AAAAPgAAAAAAAIC9AAAAvgAAAAAAAKC+AAAAPgAAAAAAAKC+AAAAvgAAAD4AAIC9AAAAPgAAAD4AAIC9AAAAvgAAAAAAAIC9AAAAPgAAAAAAAIC9AAAAPgAAAD4AAKC+AAAAvgAAAD4AAKC+AAAAPgAAAAAAAKC+AAAAvgAAAAAAAKC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgBAsD4AAP89AECwPgAAADoAwM8+AAD/PQDAzz4AQIA+AECwPgDAvz4AQLA+AECAPgDAzz4AwL8+AMDPPgCAfz4AwK8+AIAAPgDArz4AgH8+AIBgPgCAAD4AgGA+AMC/PgCAYD4AQIA+AIBgPgDAvz4AwK8+AECAPgDArz4AQMA+AECwPgDA/z4AQLA+AEDAPgDAzz4AwP8+AMDPPgCAAD4AQLA+AIB/PgBAsD4AgAA+AMDPPgCAfz4AwM8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAPQAAAD4AAKA+AAAAPQAAAD4AAIA+AAAAPQAAAAAAAKA+AAAAPQAAAAAAAIA+AAAAvQAAAD4AAIA+AAAAvQAAAD4AAKA+AAAAvQAAAAAAAIA+AAAAvQAAAAAAAKA+AAAAvQAAAD4AAIA+AAAAPQAAAD4AAIA+AAAAvQAAAD4AAKA+AAAAPQAAAD4AAKA+AAAAvQAAAAAAAKA+AAAAPQAAAAAAAKA+AAAAvQAAAAAAAIA+AAAAPQAAAAAAAIA+AAAAvQAAAD4AAKA+AAAAPQAAAD4AAKA+AAAAvQAAAAAAAKA+AAAAPQAAAAAAAKA+AAAAPQAAAD4AAIA+AAAAvQAAAD4AAIA+AAAAPQAAAAAAAIA+AAAAvQAAAAAAAIA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AEDwPgAAAj0AwP8+AAACPQBA8D4AAL89AMD/PgAAvz0AIAg/AAACPQDgDz8AAAI9ACAIPwAAvz0A4A8/AAC/PQDgBz8AAPw8ACAAPwAA/DwA4Ac/AAAAOgAgAD8AAAA6AOAPPwAAADoAIAg/AAAAOgDgDz8AAPw8ACAIPwAA/DwAIBA/AAACPQDgFz8AAAI9ACAQPwAAvz0A4Bc/AAC/PQAgAD8AAAI9AOAHPwAAAj0AIAA/AAC/PQDgBz8AAL89AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAABgPgAAAD4AAEA+AABgPgAAAD4AAAAAAABgPgAAAAAAAEA+AABgPgAAAAAAAAAAAAAAvQAAAD4AAAAAAAAAvQAAAD4AAEA+AAAAvQAAAAAAAAAAAAAAvQAAAAAAAEA+AAAAvQAAAD4AAAAAAABgPgAAAD4AAAAAAAAAvQAAAD4AAEA+AABgPgAAAD4AAEA+AAAAvQAAAAAAAEA+AABgPgAAAAAAAEA+AAAAvQAAAAAAAAAAAABgPgAAAAAAAAAAAAAAvQAAAD4AAEA+AABgPgAAAD4AAEA+AAAAvQAAAAAAAEA+AABgPgAAAAAAAEA+AABgPgAAAD4AAAAAAAAAvQAAAD4AAAAAAABgPgAAAAAAAAAAAAAAvQAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AEDQPgBA0D4AwP8+AEDQPgBA0D4AwO8+AMD/PgDA7z4AICA/AEDQPgDgNz8AQNA+ACAgPwDA7z4A4Dc/AMDvPgDgHz8AwM8+ACAAPwDAzz4A4B8/AECgPgAgAD8AQKA+AOA/PwBAoD4AICA/AECgPgDgPz8AwM8+ACAgPwDAzz4AIDg/AEDQPgDgVz8AQNA+ACA4PwDA7z4A4Fc/AMDvPgAgAD8AQNA+AOAfPwBA0D4AIAA/AMDvPgDgHz8AwO8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAABgPgAAwD0AABA/AABgPgAAwD0AAKA+AABgPgAAAD0AABA/AABgPgAAAD0AAKA+AADAPQAAwD0AAKA+AADAPQAAwD0AABA/AADAPQAAAD0AAKA+AADAPQAAAD0AABA/AADAPQAAwD0AAKA+AABgPgAAwD0AAKA+AADAPQAAwD0AABA/AABgPgAAwD0AABA/AADAPQAAAD0AABA/AABgPgAAAD0AABA/AADAPQAAAD0AAKA+AABgPgAAAD0AAKA+AADAPQAAwD0AABA/AABgPgAAwD0AABA/AADAPQAAAD0AABA/AABgPgAAAD0AABA/AABgPgAAwD0AAKA+AADAPQAAwD0AAKA+AABgPgAAAD0AAKA+AADAPQAAAD0AAKA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AECgPgAgGD8AwN8+ACAYPwBAoD4A4B8/AMDfPgDgHz8AIAA/ACAYPwDgHz8AIBg/ACAAPwDgHz8A4B8/AOAfPwDA/z4A4Bc/AEDgPgDgFz8AwP8+AEDwPgBA4D4AQPA+AOAPPwBA8D4AIAA/AEDwPgDgDz8A4Bc/ACAAPwDgFz8AICA/ACAYPwDgLz8AIBg/ACAgPwDgHz8A4C8/AOAfPwBA4D4AIBg/AMD/PgAgGD8AQOA+AOAfPwDA/z4A4B8/AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAMQjQPgAAQD4AAMA+MQjQPgAAQD4AAIA+MQjQPgAAgD0AAMA+MQjQPgAAgD0AAIA+AADQPgAAQD4AAIA+AADQPgAAQD4AAMA+AADQPgAAgD0AAIA+AADQPgAAgD0AAMA+AADQPgAAQD4AAIA+MQjQPgAAQD4AAIA+AADQPgAAQD4AAMA+MQjQPgAAQD4AAMA+AADQPgAAgD0AAMA+MQjQPgAAgD0AAMA+AADQPgAAgD0AAIA+MQjQPgAAgD0AAIA+AADQPgAAQD4AAMA+MQjQPgAAQD4AAMA+AADQPgAAgD0AAMA+MQjQPgAAgD0AAMA+MQjQPgAAQD4AAIA+AADQPgAAQD4AAIA+MQjQPgAAgD0AAIA+AADQPgAAgD0AAIA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgCAYD4AAH49AIBgPgAAADoAwI8+AAB+PQDAjz4AAIE9AIBgPgAA/z0AgGA+AACBPQDAjz4AAP89AMCPPgAAgT0AgF8+AAB+PQCAXz4AAIE9AIAgPgAAfj0AgCA+AACBPQCAID4AAH49AIAgPgAAgT0AgF8+AAB+PQCAXz4AgAA+AIBgPgAA/z0AgGA+AIAAPgDAjz4AAP89AMCPPgAAgT0AgGA+AAB+PQCAYD4AAIE9AMCPPgAAfj0AwI8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAvQAAAD4AAOA+AAAAvQAAAD4AAIA+AAAAvQAAAAAAAOA+AAAAvQAAAAAAAIA+AACQvgAAAD4AAIA+AACQvgAAAD4AAOA+AACQvgAAAAAAAIA+AACQvgAAAAAAAOA+AACQvgAAAD4AAIA+AAAAvQAAAD4AAIA+AACQvgAAAD4AAOA+AAAAvQAAAD4AAOA+AACQvgAAAAAAAOA+AAAAvQAAAAAAAOA+AACQvgAAAAAAAIA+AAAAvQAAAAAAAIA+AACQvgAAAD4AAOA+AAAAvQAAAD4AAOA+AACQvgAAAAAAAOA+AAAAvQAAAAAAAOA+AAAAvQAAAD4AAIA+AACQvgAAAD4AAIA+AAAAvQAAAAAAAIA+AACQvgAAAAAAAIA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgAgAD8AAL89ACAAPwAAADoA4A8/AAC/PQDgDz8AgGA+ACAAPwDAnz4AIAA/AIBgPgDgDz8AwJ8+AOAPPwCAXz4AwP8+AADBPQDA/z4AgF8+AEDQPgAAwT0AQNA+AMCvPgBA0D4AgGA+AEDQPgDArz4AwP8+AIBgPgDA/z4AQKA+ACAAPwDA3z4AIAA/AECgPgDgDz8AwN8+AOAPPwAAwT0AIAA/AIBfPgAgAD8AAME9AOAPPwCAXz4A4A8/AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAADAvQAAwD0AABA/AADAvQAAwD0AAKA+AADAvQAAAD0AABA/AADAvQAAAD0AAKA+AABgvgAAwD0AAKA+AABgvgAAwD0AABA/AABgvgAAAD0AAKA+AABgvgAAAD0AABA/AABgvgAAwD0AAKA+AADAvQAAwD0AAKA+AABgvgAAwD0AABA/AADAvQAAwD0AABA/AABgvgAAAD0AABA/AADAvQAAAD0AABA/AABgvgAAAD0AAKA+AADAvQAAAD0AAKA+AABgvgAAwD0AABA/AADAvQAAwD0AABA/AABgvgAAAD0AABA/AADAvQAAAD0AABA/AADAvQAAwD0AAKA+AABgvgAAwD0AAKA+AADAvQAAAD0AAKA+AABgvgAAAD0AAKA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AEDwPgCAAD4A4Bc/AIAAPgBA8D4AgB8+AOAXPwCAHz4AICg/AIAAPgDgRz8AgAA+ACAoPwCAHz4A4Ec/AIAfPgDgJz8AAP89ACAYPwAA/z0A4Cc/AAAAOgAgGD8AAAA6AOA3PwAAADoAICg/AAAAOgDgNz8AAP89ACAoPwAA/z0AIEg/AIAAPgDgVz8AgAA+ACBIPwCAHz4A4Fc/AIAfPgAgGD8AgAA+AOAnPwCAAD4AIBg/AIAfPgDgJz8AgB8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAz/fPvgAAQD4AAMA+z/fPvgAAQD4AAIA+z/fPvgAAgD0AAMA+z/fPvgAAgD0AAIA+AADQvgAAQD4AAIA+AADQvgAAQD4AAMA+AADQvgAAgD0AAIA+AADQvgAAgD0AAMA+AADQvgAAQD4AAIA+z/fPvgAAQD4AAIA+AADQvgAAQD4AAMA+z/fPvgAAQD4AAMA+AADQvgAAgD0AAMA+z/fPvgAAgD0AAMA+AADQvgAAgD0AAIA+z/fPvgAAgD0AAIA+AADQvgAAQD4AAMA+z/fPvgAAQD4AAMA+AADQvgAAgD0AAMA+z/fPvgAAgD0AAMA+z/fPvgAAQD4AAIA+AADQvgAAQD4AAIA+z/fPvgAAgD0AAIA+AADQvgAAgD0AAIA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgAAgT0AAH49AACBPQAAADoAAP89AAB+PQAA/z0AAIE9AACBPQAA/z0AAIE9AACBPQAA/z0AAP89AAD/PQAAgT0AAH49AAB+PQAAfj0AAIE9AAAAOgAAfj0AAAA6AACBPQAAADoAAH49AAAAOgAAgT0AAH49AAB+PQAAfj0AgAA+AACBPQAA/z0AAIE9AIAAPgAA/z0AAP89AAD/PQAAgT0AAIE9AAB+PQAAgT0AAIE9AAD/PQAAfj0AAP89AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAACAPgAAwD0AAMC9AACAPgAAwD0AACC+AACAPgAAAD0AAMC9AACAPgAAAD0AACC+AAAAPgAAwD0AACC+AAAAPgAAwD0AAMC9AAAAPgAAAD0AACC+AAAAPgAAAD0AAMC9AAAAPgAAwD0AACC+AACAPgAAwD0AACC+AAAAPgAAwD0AAMC9AACAPgAAwD0AAMC9AAAAPgAAAD0AAMC9AACAPgAAAD0AAMC9AAAAPgAAAD0AACC+AACAPgAAAD0AACC+AAAAPgAAwD0AAMC9AACAPgAAwD0AAMC9AAAAPgAAAD0AAMC9AACAPgAAAD0AAMC9AACAPgAAwD0AACC+AAAAPgAAwD0AACC+AACAPgAAAD0AACC+AAAAPgAAAD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/ACAIPwBAkD4A4A8/AECQPgAgCD8AwJ8+AOAPPwDAnz4AICA/AECQPgDgJz8AQJA+ACAgPwDAnz4A4Cc/AMCfPgDgHz8AwI8+ACAQPwDAjz4A4B8/AECAPgAgED8AQIA+AOAvPwBAgD4AICA/AECAPgDgLz8AwI8+ACAgPwDAjz4AICg/AECQPgDgNz8AQJA+ACAoPwDAnz4A4Dc/AMCfPgAgED8AQJA+AOAfPwBAkD4AIBA/AMCfPgDgHz8AwJ8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAACQPgAAwD0AAMC9AACQPgAAwD0AACC+AACQPgAAAD0AAMC9AACQPgAAAD0AACC+AABgPgAAwD0AACC+AABgPgAAwD0AAMC9AABgPgAAAD0AACC+AABgPgAAAD0AAMC9AABgPgAAwD0AACC+AACQPgAAwD0AACC+AABgPgAAwD0AAMC9AACQPgAAwD0AAMC9AABgPgAAAD0AAMC9AACQPgAAAD0AAMC9AABgPgAAAD0AACC+AACQPgAAAD0AACC+AABgPgAAwD0AAMC9AACQPgAAwD0AAMC9AABgPgAAAD0AAMC9AACQPgAAAD0AAMC9AACQPgAAwD0AACC+AABgPgAAwD0AACC+AACQPgAAAD0AACC+AABgPgAAAD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgBAoD4AAPw8AECgPgAAADoAwK8+AAD8PADArz4AAIE9AECgPgAAvz0AQKA+AACBPQDArz4AAL89AMCvPgAAfj0AwJ8+AAACPQDAnz4AAH49AECQPgAAAj0AQJA+AAC/PQBAkD4AAIE9AECQPgAAvz0AwJ8+AACBPQDAnz4AAME9AECgPgAA/z0AQKA+AADBPQDArz4AAP89AMCvPgAAAj0AQKA+AAB+PQBAoD4AAAI9AMCvPgAAfj0AwK8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAACwPsUggD0AAMC9AACwPsUggD0AACC+AACwPgAAgD0AAMC9AACwPgAAgD0AACC+AACQPsUggD0AACC+AACQPsUggD0AAMC9AACQPgAAgD0AACC+AACQPgAAgD0AAMC9AACQPsUggD0AACC+AACwPsUggD0AACC+AACQPsUggD0AAMC9AACwPsUggD0AAMC9AACQPgAAgD0AAMC9AACwPgAAgD0AAMC9AACQPgAAgD0AACC+AACwPgAAgD0AACC+AACQPsUggD0AAMC9AACwPsUggD0AAMC9AACQPgAAgD0AAMC9AACwPgAAgD0AAMC9AACwPsUggD0AACC+AACQPsUggD0AACC+AACwPgAAgD0AACC+AACQPgAAgD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgCAID4AAPw8AIAgPgAAADoAgB8+AAD8PACAHz4AAIE9AIAgPgAAvz0AgCA+AACBPQCAHz4AAL89AIAfPgAAfj0AgB8+AAACPQCAHz4AAH49AIAAPgAAAj0AgAA+AAC/PQCAAD4AAIE9AIAAPgAAvz0AgB8+AACBPQCAHz4AAME9AIAgPgAA/z0AgCA+AADBPQCAHz4AAP89AIAfPgAAAj0AgCA+AAB+PQCAID4AAAI9AIAfPgAAfj0AgB8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAvgAAwD0AAMC9AAAAvgAAwD0AACC+AAAAvgAAAD0AAMC9AAAAvgAAAD0AACC+AACAvgAAwD0AACC+AACAvgAAwD0AAMC9AACAvgAAAD0AACC+AACAvgAAAD0AAMC9AACAvgAAwD0AACC+AAAAvgAAwD0AACC+AACAvgAAwD0AAMC9AAAAvgAAwD0AAMC9AACAvgAAAD0AAMC9AAAAvgAAAD0AAMC9AACAvgAAAD0AACC+AAAAvgAAAD0AACC+AACAvgAAwD0AAMC9AAAAvgAAwD0AAMC9AACAvgAAAD0AAMC9AAAAvgAAAD0AAMC9AAAAvgAAwD0AACC+AACAvgAAwD0AACC+AAAAvgAAAD0AACC+AACAvgAAAD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AEDAPgBAgD4AwM8+AECAPgBAwD4AwI8+AMDPPgDAjz4AQPA+AECAPgDA/z4AQIA+AEDwPgDAjz4AwP8+AMCPPgDA7z4AgH8+AEDQPgCAfz4AwO8+AIBgPgBA0D4AgGA+AOAHPwCAYD4AQPA+AIBgPgDgBz8AgH8+AEDwPgCAfz4AIAA/AECAPgDgDz8AQIA+ACAAPwDAjz4A4A8/AMCPPgBA0D4AQIA+AMDvPgBAgD4AQNA+AMCPPgDA7z4AwI8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAABgvgAAwD0AAMC9AABgvgAAwD0AACC+AABgvgAAAD0AAMC9AABgvgAAAD0AACC+AACQvgAAwD0AACC+AACQvgAAwD0AAMC9AACQvgAAAD0AACC+AACQvgAAAD0AAMC9AACQvgAAwD0AACC+AABgvgAAwD0AACC+AACQvgAAwD0AAMC9AABgvgAAwD0AAMC9AACQvgAAAD0AAMC9AABgvgAAAD0AAMC9AACQvgAAAD0AACC+AABgvgAAAD0AACC+AACQvgAAwD0AAMC9AABgvgAAwD0AAMC9AACQvgAAAD0AAMC9AABgvgAAAD0AAMC9AABgvgAAwD0AACC+AACQvgAAwD0AACC+AABgvgAAAD0AACC+AACQvgAAAD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgAAAj0AAPw8AAACPQAAADoAAH49AAD8PAAAfj0AAIE9AAACPQAAvz0AAAI9AACBPQAAfj0AAL89AAB+PQAAfj0AAPw8AAACPQAA/DwAAH49AAAAOgAAAj0AAAA6AAC/PQAAADoAAIE9AAAAOgAAvz0AAPw8AACBPQAA/DwAAME9AAACPQAA/z0AAAI9AADBPQAAfj0AAP89AAB+PQAAAj0AAAI9AAB+PQAAAj0AAAI9AAB+PQAAfj0AAH49AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAACQvsUggD0AAMC9AACQvsUggD0AACC+AACQvgAAgD0AAMC9AACQvgAAgD0AACC+AACwvsUggD0AACC+AACwvsUggD0AAMC9AACwvgAAgD0AACC+AACwvgAAgD0AAMC9AACwvsUggD0AACC+AACQvsUggD0AACC+AACwvsUggD0AAMC9AACQvsUggD0AAMC9AACwvgAAgD0AAMC9AACQvgAAgD0AAMC9AACwvgAAgD0AACC+AACQvgAAgD0AACC+AACwvsUggD0AAMC9AACQvsUggD0AAMC9AACwvgAAgD0AAMC9AACQvgAAgD0AAMC9AACQvsUggD0AACC+AACwvsUggD0AACC+AACQvgAAgD0AACC+AACwvgAAgD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACBPQAAAj0AAL89AAACPQAAgT0AAPw8AAC/PQAA/DwAgAA+AAACPQCAHz4AAAI9AIAAPgAA/DwAgB8+AAD8PAAA/z0AAPw8AADBPQAA/DwAAP89AAAAOgAAwT0AAAA6AIAfPgAAADoAgAA+AAAAOgCAHz4AAPw8AIAAPgAA/DwAgCA+AAACPQCAPz4AAAI9AIAgPgAA/DwAgD8+AAD8PAAAwT0AAAI9AAD/PQAAAj0AAME9AAD8PAAA/z0AAPw8AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPxPyhT0AAAAAAAAAAK9zfz8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPwAAAADug4Q+AAAAAOpGdz8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPwAAAADug4S+AAAAAOpGdz8AAAAAIbWyvAAAAABn8H8/AAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPwAAAACoqAU+AAAAAFXPfT8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPwAAAACoqAW+AAAAAFXPfT8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAD+rqko/AAAAAAAAAAAAAAAAAACAPwAAAAAhtbI8AAAAAGfwfz8AAAAAx71QPAAAAACu+n8/AAAAAAAAAD+rqko/AAAAAAAAAAAAAAAAAACAPwAAAAAhtbK8AAAAAGfwfz8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAQD8AAMA/AAAAAAAAAAAAAAAAAACAPwAAAAC2frK9AAAAAJ4Gfz8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAQD8AAMA/AAAAAAAAAAAAAAAAAACAPwAAAAC2frI9AAAAAJ4Gfz8AAAAAAAAAAAAAAAAAAIA/AAAAAKioBb4AAAAAAAAAAFXPfT8AAIA+qKgFvgAAAAAAAAAAVc99Pw=="}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[0.15625,0.125,0.25],"min":[-0.15625,0,-0.0625],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"max":[0.62451171875,0.21826171875],"min":[0.00048828125,0.00048828125],"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":4,"componentType":5126,"count":24,"max":[0.125,0.125,-0.0625],"min":[-0.125,0,-0.3125],"type":"VEC3"},{"bufferView":5,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":6,"componentType":5126,"count":24,"max":[0.49951171875,0.40576171875],"min":[0.00048828125,0.21923828125],"type":"VEC2"},{"bufferView":7,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":8,"componentType":5126,"count":24,"max":[0.03125,0.125,0.3125],"min":[-0.03125,0,0.25],"type":"VEC3"},{"bufferView":9,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":10,"componentType":5126,"count":24,"max":[0.59326171875,0.09326171875],"min":[0.46923828125,0.00048828125],"type":"VEC2"},{"bufferView":11,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":12,"componentType":5126,"count":24,"max":[0.21875,0.125,0.1875],"min":[-0.03125,0,0],"type":"VEC3"},{"bufferView":13,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":14,"componentType":5126,"count":24,"max":[0.84326171875,0.46826171875],"min":[0.40673828125,0.31298828125],"type":"VEC2"},{"bufferView":15,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":16,"componentType":5126,"count":24,"max":[0.21875,0.09375,0.5625],"min":[0.09375,0.03125,0.3125],"type":"VEC3"},{"bufferView":17,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":18,"componentType":5126,"count":24,"max":[0.68701171875,0.62451171875],"min":[0.31298828125,0.46923828125],"type":"VEC2"},{"bufferView":19,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":20,"componentType":5126,"count":24,"max":[0.406312495470047,0.1875,0.375],"min":[0.40625,0.0625,0.25],"type":"VEC3"},{"bufferView":21,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":22,"componentType":5126,"count":24,"max":[0.12548828125,0.28076171875],"min":[0.00048828125,0.15673828125],"type":"VEC2"},{"bufferView":23,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":24,"componentType":5126,"count":24,"max":[-0.03125,0.125,0.4375],"min":[-0.28125,0,0.25],"type":"VEC3"},{"bufferView":25,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":26,"componentType":5126,"count":24,"max":[0.43701171875,0.56201171875],"min":[0.00048828125,0.40673828125],"type":"VEC2"},{"bufferView":27,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":28,"componentType":5126,"count":24,"max":[-0.09375,0.09375,0.5625],"min":[-0.21875,0.03125,0.3125],"type":"VEC3"},{"bufferView":29,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":30,"componentType":5126,"count":24,"max":[0.84326171875,0.15576171875],"min":[0.46923828125,0.00048828125],"type":"VEC2"},{"bufferView":31,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":32,"componentType":5126,"count":24,"max":[-0.406187504529953,0.1875,0.375],"min":[-0.40625,0.0625,0.25],"type":"VEC3"},{"bufferView":33,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":34,"componentType":5126,"count":24,"max":[0.12548828125,0.12451171875],"min":[0.00048828125,0.00048828125],"type":"VEC2"},{"bufferView":35,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":36,"componentType":5126,"count":24,"max":[0.25,0.09375,-0.09375],"min":[0.125,0.03125,-0.15625],"type":"VEC3"},{"bufferView":37,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":38,"componentType":5126,"count":24,"max":[0.71826171875,0.31201171875],"min":[0.53173828125,0.25048828125],"type":"VEC2"},{"bufferView":39,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":40,"componentType":5126,"count":24,"max":[0.28125,0.09375,-0.09375],"min":[0.21875,0.03125,-0.15625],"type":"VEC3"},{"bufferView":41,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":42,"componentType":5126,"count":24,"max":[0.12451171875,0.34326171875],"min":[0.00048828125,0.28173828125],"type":"VEC2"},{"bufferView":43,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":44,"componentType":5126,"count":24,"max":[0.34375,0.0625625029206276,-0.09375],"min":[0.28125,0.0625,-0.15625],"type":"VEC3"},{"bufferView":45,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":46,"componentType":5126,"count":24,"max":[0.12451171875,0.15673828125],"min":[0.00048828125,0.12548828125],"type":"VEC2"},{"bufferView":47,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":48,"componentType":5126,"count":24,"max":[-0.125,0.09375,-0.09375],"min":[-0.25,0.03125,-0.15625],"type":"VEC3"},{"bufferView":49,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":50,"componentType":5126,"count":24,"max":[0.56201171875,0.28076171875],"min":[0.37548828125,0.21923828125],"type":"VEC2"},{"bufferView":51,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":52,"componentType":5126,"count":24,"max":[-0.21875,0.09375,-0.09375],"min":[-0.28125,0.03125,-0.15625],"type":"VEC3"},{"bufferView":53,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":54,"componentType":5126,"count":24,"max":[0.12451171875,0.06201171875],"min":[0.00048828125,0.00048828125],"type":"VEC2"},{"bufferView":55,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":56,"componentType":5126,"count":24,"max":[-0.28125,0.0625625029206276,-0.09375],"min":[-0.34375,0.0625,-0.15625],"type":"VEC3"},{"bufferView":57,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":58,"componentType":5126,"count":24,"max":[0.18701171875,0.03173828125],"min":[0.06298828125,0.00048828125],"type":"VEC2"},{"bufferView":59,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":60,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":61,"componentType":5126,"count":3,"max":[0.06540312618017197,0,0,1],"min":[0,0,0,0.9978589415550232],"type":"VEC4"},{"bufferView":62,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":63,"componentType":5126,"count":3,"max":[0,0.258819043636322,0,1],"min":[0,0,0,0.9659258127212524],"type":"VEC4"},{"bufferView":64,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":65,"componentType":5126,"count":3,"max":[0,0,0,1],"min":[0,-0.258819043636322,0,0.9659258127212524],"type":"VEC4"},{"bufferView":66,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":67,"componentType":5126,"count":3,"max":[0,0.13052618503570557,0,1],"min":[0,0,0,0.9914448857307434],"type":"VEC4"},{"bufferView":68,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":69,"componentType":5126,"count":3,"max":[0,0,0,1],"min":[0,-0.13052618503570557,0,0.9914448857307434],"type":"VEC4"},{"bufferView":70,"componentType":5126,"count":3,"max":[0.7916666865348816],"min":[0],"type":"SCALAR"},{"bufferView":71,"componentType":5126,"count":3,"max":[0,0.02181488461792469,0,1],"min":[0,0,0,0.9997619986534119],"type":"VEC4"},{"bufferView":72,"componentType":5126,"count":3,"max":[0.7916666865348816],"min":[0],"type":"SCALAR"},{"bufferView":73,"componentType":5126,"count":3,"max":[0,0,0,1],"min":[0,-0.02181488461792469,0,0.9997619986534119],"type":"VEC4"},{"bufferView":74,"componentType":5126,"count":3,"max":[1.5],"min":[0],"type":"SCALAR"},{"bufferView":75,"componentType":5126,"count":3,"max":[0,0,0,1],"min":[0,-0.08715574443340302,0,0.9961947202682495],"type":"VEC4"},{"bufferView":76,"componentType":5126,"count":3,"max":[1.5],"min":[0],"type":"SCALAR"},{"bufferView":77,"componentType":5126,"count":3,"max":[0,0.08715574443340302,0,1],"min":[0,0,0,0.9961947202682495],"type":"VEC4"},{"bufferView":78,"componentType":5126,"count":1,"max":[0],"min":[0],"type":"SCALAR"},{"bufferView":79,"componentType":5126,"count":1,"max":[-0.13052618503570557,0,0,0.9914448857307434],"min":[-0.13052618503570557,0,0,0.9914448857307434],"type":"VEC4"},{"bufferView":80,"componentType":5126,"count":1,"max":[0.25],"min":[0.25],"type":"SCALAR"},{"bufferView":81,"componentType":5126,"count":1,"max":[-0.13052618503570557,0,0,0.9914448857307434],"min":[-0.13052618503570557,0,0,0.9914448857307434],"type":"VEC4"}],"materials":[{"pbrMetallicRoughness":{"metallicFactor":0,"roughnessFactor":1,"baseColorTexture":{"index":0}},"alphaMode":"MASK","alphaCutoff":0.05,"doubleSided":true}],"textures":[{"sampler":0}],"samplers":[{"magFilter":9728,"minFilter":9728,"wrapS":33071,"wrapT":33071}],"meshes":[{"primitives":[{"mode":4,"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":4,"NORMAL":5,"TEXCOORD_0":6},"indices":7,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":8,"NORMAL":9,"TEXCOORD_0":10},"indices":11,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":12,"NORMAL":13,"TEXCOORD_0":14},"indices":15,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":16,"NORMAL":17,"TEXCOORD_0":18},"indices":19,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":20,"NORMAL":21,"TEXCOORD_0":22},"indices":23,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":24,"NORMAL":25,"TEXCOORD_0":26},"indices":27,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":28,"NORMAL":29,"TEXCOORD_0":30},"indices":31,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":32,"NORMAL":33,"TEXCOORD_0":34},"indices":35,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":36,"NORMAL":37,"TEXCOORD_0":38},"indices":39,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":40,"NORMAL":41,"TEXCOORD_0":42},"indices":43,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":44,"NORMAL":45,"TEXCOORD_0":46},"indices":47,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":48,"NORMAL":49,"TEXCOORD_0":50},"indices":51,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":52,"NORMAL":53,"TEXCOORD_0":54},"indices":55,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":56,"NORMAL":57,"TEXCOORD_0":58},"indices":59,"material":0}]}],"animations":[{"name":"animation.model.walk","samplers":[{"input":60,"output":61,"interpolation":"LINEAR"},{"input":62,"output":63,"interpolation":"LINEAR"},{"input":64,"output":65,"interpolation":"LINEAR"},{"input":66,"output":67,"interpolation":"LINEAR"},{"input":68,"output":69,"interpolation":"LINEAR"}],"channels":[{"sampler":0,"target":{"node":3,"path":"rotation"}},{"sampler":1,"target":{"node":7,"path":"rotation"}},{"sampler":2,"target":{"node":11,"path":"rotation"}},{"sampler":3,"target":{"node":15,"path":"rotation"}},{"sampler":4,"target":{"node":19,"path":"rotation"}}]},{"name":"animation.model.idle","samplers":[{"input":70,"output":71,"interpolation":"LINEAR"},{"input":72,"output":73,"interpolation":"LINEAR"},{"input":74,"output":75,"interpolation":"LINEAR"},{"input":76,"output":77,"interpolation":"LINEAR"}],"channels":[{"sampler":0,"target":{"node":7,"path":"rotation"}},{"sampler":1,"target":{"node":11,"path":"rotation"}},{"sampler":2,"target":{"node":15,"path":"rotation"}},{"sampler":3,"target":{"node":19,"path":"rotation"}}]},{"name":"animation.model.back","samplers":[{"input":78,"output":79,"interpolation":"LINEAR"},{"input":80,"output":81,"interpolation":"LINEAR"}],"channels":[{"sampler":0,"target":{"node":15,"path":"rotation"}},{"sampler":1,"target":{"node":19,"path":"rotation"}}]}]} diff --git a/games/devtest/mods/gltf/models/gltf_minimal_triangle.gltf b/games/devtest/mods/gltf/models/gltf_minimal_triangle.gltf new file mode 100644 index 000000000..9a624f085 --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_minimal_triangle.gltf @@ -0,0 +1 @@ +{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":1},"indices":0}]}],"buffers":[{"uri":"data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=","byteLength":44}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":6,"target":34963},{"buffer":0,"byteOffset":8,"byteLength":36,"target":34962}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5123,"count":3,"type":"SCALAR","max":[2],"min":[0]},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":3,"type":"VEC3","max":[1,1,0],"min":[0,0,0]}],"asset":{"version":"2.0"}} diff --git a/games/devtest/mods/gltf/models/gltf_simple_sparse_accessor.gltf b/games/devtest/mods/gltf/models/gltf_simple_sparse_accessor.gltf new file mode 100644 index 000000000..979896825 --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_simple_sparse_accessor.gltf @@ -0,0 +1 @@ +{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":1},"indices":0}]}],"buffers":[{"uri":"data:application/gltf-buffer;base64,AAAIAAcAAAABAAgAAQAJAAgAAQACAAkAAgAKAAkAAgADAAoAAwALAAoAAwAEAAsABAAMAAsABAAFAAwABQANAAwABQAGAA0AAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAQAAAAAAAAAAAAABAQAAAAAAAAAAAAACAQAAAAAAAAAAAAACgQAAAAAAAAAAAAADAQAAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAQAAAgD8AAAAAAABAQAAAgD8AAAAAAACAQAAAgD8AAAAAAACgQAAAgD8AAAAAAADAQAAAgD8AAAAACAAKAAwAAAAAAIA/AAAAQAAAAAAAAEBAAABAQAAAAAAAAKBAAACAQAAAAAA=","byteLength":284}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":72,"byteLength":168},{"buffer":0,"byteOffset":240,"byteLength":6},{"buffer":0,"byteOffset":248,"byteLength":36}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5123,"count":36,"type":"SCALAR","max":[13],"min":[0]},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":14,"type":"VEC3","max":[6,4,0],"min":[0,0,0],"sparse":{"count":3,"indices":{"bufferView":2,"byteOffset":0,"componentType":5123},"values":{"bufferView":3,"byteOffset":0}}}],"asset":{"version":"2.0"}} diff --git a/games/devtest/mods/gltf/models/gltf_snow_man.gltf b/games/devtest/mods/gltf/models/gltf_snow_man.gltf new file mode 100644 index 000000000..cd8c347d2 --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_snow_man.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"Blockbench 4.6.0 glTF exporter"},"scenes":[{"nodes":[3],"name":"blockbench_export"}],"scene":0,"nodes":[{"name":"cube","mesh":0},{"name":"cube","mesh":1},{"name":"cube","mesh":2},{"children":[0,1,2]}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":288,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":576,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":768,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":840,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1128,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1416,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":1608,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":1680,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1968,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":2256,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":2448,"byteLength":72,"target":34963}],"buffers":[{"byteLength":2520,"uri":"data:application/octet-stream;base64,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"}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[3,24,3],"min":[-3,18,-3],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"max":[0.8333333134651184,1],"min":[0,0.25],"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":4,"componentType":5126,"count":24,"max":[5,10,5],"min":[-5,0,-5],"type":"VEC3"},{"bufferView":5,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":6,"componentType":5126,"count":24,"max":[0.625,1],"min":[0,0.375],"type":"VEC2"},{"bufferView":7,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":8,"componentType":5126,"count":24,"max":[4,18,4],"min":[-4,10,-4],"type":"VEC3"},{"bufferView":9,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":10,"componentType":5126,"count":24,"max":[0.7083333134651184,0.7916666865348816],"min":[0.2083333283662796,0.25],"type":"VEC2"},{"bufferView":11,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"}],"materials":[{"pbrMetallicRoughness":{"metallicFactor":0,"roughnessFactor":1,"baseColorTexture":{"index":0,"texCoord":0}},"alphaMode":"MASK","alphaCutoff":0.05,"doubleSided":true}],"textures":[{}],"meshes":[{"primitives":[{"mode":4,"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":4,"NORMAL":5,"TEXCOORD_0":6},"indices":7,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":8,"NORMAL":9,"TEXCOORD_0":10},"indices":11,"material":0}]}]} diff --git a/games/devtest/mods/gltf/models/gltf_spider.gltf b/games/devtest/mods/gltf/models/gltf_spider.gltf new file mode 100644 index 000000000..6698b6bb4 --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_spider.gltf @@ -0,0 +1 @@ +{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[0]}],"nodes":[{"mesh":0,"name":"Spider"}],"materials":[{"doubleSided":true,"name":"Material.001","pbrMetallicRoughness":{}}],"meshes":[{"name":"Cube","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3,"material":0}]}],"accessors":[{"bufferView":0,"componentType":5126,"count":1000,"max":[2.742279291152954,1.4045029878616333,2.0192716121673584],"min":[-2.742279291152954,-0.6434623599052429,-3.534085512161255],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":1000,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1000,"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":1500,"type":"SCALAR"}],"bufferViews":[{"buffer":0,"byteLength":12000,"byteOffset":0},{"buffer":0,"byteLength":12000,"byteOffset":12000},{"buffer":0,"byteLength":8000,"byteOffset":24000},{"buffer":0,"byteLength":3000,"byteOffset":32000}],"buffers":[{"byteLength":35000,"uri":"data:application/octet-stream;base64,dfkpP+R6/z6QwIW/dfkpP+R6/z6QwIW/dfkpP+R6/z6QwIW/dfkpP+R6/76QwIW/dfkpP+R6/76QwIW/dfkpP+R6/76QwIW/dfkpP+R6/z6QwIU/dfkpP+R6/z6QwIU/dfkpP+R6/z6QwIU/dfkpP+R6/76QwIU/dfkpP+R6/76QwIU/dfkpP+R6/76QwIU/dfkpv+R6/z6QwIW/dfkpv+R6/z6QwIW/dfkpv+R6/z6QwIW/dfkpv+R6/76QwIW/dfkpv+R6/76QwIW/dfkpv+R6/76QwIW/dfkpv+R6/z6QwIU/dfkpv+R6/z6QwIU/dfkpv+R6/z6QwIU/dfkpv+R6/76QwIU/dfkpv+R6/76QwIU/dfkpv+R6/76QwIU/UoRdPwFMoz8qkU3AUoRdPwFMoz8qkU3AUoRdPwFMoz8qkU3AUoRdP+x7gDx1LmLAUoRdP+x7gDx1LmLAUoRdP+x7gDx1LmLAbCVCP/IRET8e1xe/bCVCP/IRET8e1xe/bCVCP/IRET8e1xe/bCVCP5SmCb8LHGC/bCVCP5SmCb8LHGC/bCVCP5SmCb8LHGC/UoRdvwFMoz8qkU3AUoRdvwFMoz8qkU3AUoRdvwFMoz8qkU3AUoRdv+x7gDx1LmLAUoRdv+x7gDx1LmLAUoRdv+x7gDx1LmLAbCVCv/IRET8e1xe/bCVCv/IRET8e1xe/bCVCv/IRET8e1xe/bCVCv5SmCb8LHGC/bCVCv5SmCb8LHGC/bCVCv5SmCb8LHGC/XiXDvkD14r7OlcU/XiXDvkD14r7OlcU/XiXDvkD14r7OlcU/XiXDvhwyo71XteY/XiXDvhwyo71XteY/XiXDvhwyo71XteY/XiXDvhwyoz1zEE8/XiXDvhwyoz1zEE8/XiXDvhwyoz1zEE8/XiXDvkD14j7Dp4g/XiXDvkD14j7Dp4g/XiXDvkD14j7Dp4g/XCXDPkD14r7OlcU/XCXDPkD14r7OlcU/XCXDPkD14r7OlcU/XCXDPhwyo71XteY/XCXDPhwyo71XteY/XCXDPhwyo71XteY/XCXDPhwyoz1zEE8/XCXDPhwyoz1zEE8/XCXDPhwyoz1zEE8/XCXDPkD14j7Dp4g/XCXDPkD14j7Dp4g/XCXDPkD14j7Dp4g/bi6Dv7og4L5LpA/Abi6Dv7og4L5LpA/Abi6Dv7og4L5LpA/Abi6Dv7og4L5LpA/Abi6DP27/hj/Sc+6/bi6DP27/hj/Sc+6/bi6DP27/hj/Sc+6/bi6DP27/hj/Sc+6/bi6Dv27/hj/Uc+6/bi6Dv27/hj/Uc+6/bi6Dv27/hj/Uc+6/bi6Dv27/hj/Uc+6/bi6DP7wg4L5LpA/Abi6DP7wg4L5LpA/Abi6DP7wg4L5LpA/Abi6DP7wg4L5LpA/AKXA/vg7sv76Nef0/KXA/vg7sv76Nef0/KXA/vg7sv76Nef0/KXA/voixaL6/OwFAKXA/voixaL6/OwFAKXA/voixaL6/OwFAMSWTvg+jir5UDss/MSWTvg+jir5UDss/MSWTvg+jir5UDss/MSWTvg4//L1HDNA/MSWTvg4//L1HDNA/MSWTvg4//L1HDNA/A5UevXQku77E8/g/A5UevXQku77E8/g/A5UevXQku77E8/g/A5UevVIiX7648f0/A5UevVIiX7648f0/A5UevVIiX7648f0/eH8OvnTbhb6MiMY/eH8OvnTbhb6MiMY/eH8OvnTbhb6MiMY/eH8Ovqcg6b1/hss/eH8Ovqcg6b1/hss/eH8Ovqcg6b1/hss/B61WvpRDlr0p2+w/B61WvpRDlr0p2+w/B61WvpRDlr0p2+w/B61WvoT9Ej6Ek98/B61WvoT9Ej6Ek98/B61WvoT9Ej6Ek98/B61Wvov9Er55E9o/B61Wvov9Er55E9o/B61Wvov9Er55E9o/B61WvodDlj3Vy8w/B61WvodDlj3Vy8w/B61WvodDlj3Vy8w/f4pAvZRDlr0p2+w/f4pAvZRDlr0p2+w/f4pAvZRDlr0p2+w/f4pAvYT9Ej6Ek98/f4pAvYT9Ej6Ek98/f4pAvYT9Ej6Ek98/f4pAvYv9Er55E9o/f4pAvYv9Er55E9o/f4pAvYv9Er55E9o/f4pAvYdDlj3Vy8w/f4pAvYdDlj3Vy8w/f4pAvYdDlj3Vy8w/8CCuvr/lGL1K++Q/8CCuvr/lGL1K++Q/8CCuvr/lGL1K++Q/8CCuvvWQlT2tOd4/8CCuvvWQlT2tOd4/8CCuvvWQlT2tOd4/8CCuvgGRlb1Rbds/8CCuvgGRlb1Rbds/8CCuvgGRlb1Rbds/8CCuvqTlGD20q9Q/8CCuvqTlGD20q9Q/8CCuvqTlGD20q9Q/jMODvr/lGL1K++Q/jMODvr/lGL1K++Q/jMODvr/lGL1K++Q/jMODvvWQlT2tOd4/jMODvvWQlT2tOd4/jMODvvWQlT2tOd4/jMODvgGRlb1Rbds/jMODvgGRlb1Rbds/jMODvgGRlb1Rbds/jMODvqTlGD20q9Q/jMODvqTlGD20q9Q/jMODvqTlGD20q9Q/KXA/Pg7sv76Nef0/KXA/Pg7sv76Nef0/KXA/Pg7sv76Nef0/KXA/PoixaL6/OwFAKXA/PoixaL6/OwFAKXA/PoixaL6/OwFAMSWTPg+jir5UDss/MSWTPg+jir5UDss/MSWTPg+jir5UDss/MSWTPg4//L1HDNA/MSWTPg4//L1HDNA/MSWTPg4//L1HDNA/A5UePXQku77E8/g/A5UePXQku77E8/g/A5UePXQku77E8/g/A5UePVIiX7648f0/A5UePVIiX7648f0/A5UePVIiX7648f0/eH8OPnTbhb6MiMY/eH8OPnTbhb6MiMY/eH8OPnTbhb6MiMY/eH8OPqcg6b1/hss/eH8OPqcg6b1/hss/eH8OPqcg6b1/hss/B61WPpRDlr0p2+w/B61WPpRDlr0p2+w/B61WPpRDlr0p2+w/B61WPoT9Ej6Ek98/B61WPoT9Ej6Ek98/B61WPoT9Ej6Ek98/B61WPov9Er55E9o/B61WPov9Er55E9o/B61WPov9Er55E9o/B61WPodDlj3Vy8w/B61WPodDlj3Vy8w/B61WPodDlj3Vy8w/f4pAPZRDlr0p2+w/f4pAPZRDlr0p2+w/f4pAPZRDlr0p2+w/f4pAPYT9Ej6Ek98/f4pAPYT9Ej6Ek98/f4pAPYT9Ej6Ek98/f4pAPYv9Er55E9o/f4pAPYv9Er55E9o/f4pAPYv9Er55E9o/f4pAPYdDlj3Vy8w/f4pAPYdDlj3Vy8w/f4pAPYdDlj3Vy8w/8CCuPr/lGL1K++Q/8CCuPr/lGL1K++Q/8CCuPr/lGL1K++Q/8CCuPvWQlT2tOd4/8CCuPvWQlT2tOd4/8CCuPvWQlT2tOd4/8CCuPgGRlb1Rbds/8CCuPgGRlb1Rbds/8CCuPgGRlb1Rbds/8CCuPqTlGD20q9Q/8CCuPqTlGD20q9Q/8CCuPqTlGD20q9Q/jMODPr/lGL1K++Q/jMODPr/lGL1K++Q/jMODPr/lGL1K++Q/jMODPvWQlT2tOd4/jMODPvWQlT2tOd4/jMODPvWQlT2tOd4/jMODPgGRlb1Rbds/jMODPgGRlb1Rbds/jMODPgGRlb1Rbds/jMODPqTlGD20q9Q/jMODPqTlGD20q9Q/jMODPqTlGD20q9Q/irGqvwXbij8FXqI/irGqvwXbij8FXqI/irGqvwXbij8FXqI/ORyOv3F4mT/sD5c/ORyOv3F4mT/sD5c/ORyOv3F4mT/sD5c/veG1vwXbij9MFIY/veG1vwXbij9MFIY/veG1vwXbij9MFIY/bEyZv3F4mT9jjHU/bEyZv3F4mT9jjHU/bEyZv3F4mT9jjHU/6Wwlv2yF8L6qI38/6Wwlv2yF8L6qI38/6Wwlv2yF8L6qI38/ioTYvrQPtr54h2g/ioTYvrQPtr54h2g/ioTYvrQPtr54h2g/T807v2yF8L43kEY/T807v2yF8L43kEY/T807v2yF8L43kEY/raICv7QPtr4D9C8/raICv7QPtr4D9C8/raICv7QPtr4D9C8/z+YCwI6slj+TWsQ/z+YCwI6slj+TWsQ/z+YCwI6slj+TWsQ/A/f/v8HGsz9xC8I/A/f/v8HGsz9xC8I/A/f/v8HGsz9xC8I/jMsHwI6slj/Wm6s/jMsHwI6slj/Wm6s/jMsHwI6slj/Wm6s/PuAEwMHGsz+yTKk/PuAEwMHGsz+yTKk/PuAEwMHGsz+yTKk/R9OVv6Pudz+mEJg/R9OVv6Pudz+mEJg/R9OVv6Pudz+mEJg/rfyPv4YRmT+EwZU/rfyPv4YRmT+EwZU/rfyPv4YRmT+EwZU/wZyfv6Pudz/So34/wZyfv6Pudz/So34/wZyfv6Pudz/So34/J8aZv4QRmT+MBXo/J8aZv4QRmT+MBXo/J8aZv4QRmT+MBXo/iI4EwFQ4sz8QDKk/iI4EwFQ4sz8QDKk/iI4EwFQ4sz8QDKk/dS/zv7wLoT/OX6A/dS/zv7wLoT/OX6A/dS/zv7wLoT/OX6A/mVP/v1I4sz/PysE/mVP/v1I4sz/PysE/mVP/v1I4sz/PysE/+2Xpv7wLoT+MHrk/+2Xpv7wLoT+MHrk/+2Xpv7wLoT+MHrk/6tMnwDbsIz+L8sQ/6tMnwDbsIz+L8sQ/6tMnwDbsIz+L8sQ/HN0cwAkm/z5JRrw/HN0cwAkm/z5JRrw/HN0cwAkm/z5JRrw/Le8iwDbsIz9Jsd0/Le8iwDbsIz9Jsd0/Le8iwDbsIz9Jsd0/YPgXwAkm/z4HBdU/YPgXwAkm/z4HBdU/YPgXwAkm/z4HBdU/GQohwGb1Jz9Bcto/GQohwGb1Jz9Bcto/GQohwGb1Jz9Bcto/pBcVwGyGMT/v/tA/pBcVwGyGMT/v/tA/pBcVwGyGMT/v/tA/2FUlwGb1Jz8jucQ/2FUlwGb1Jz8jucQ/2FUlwGb1Jz8jucQ/ZGMZwGyGMT/QRbs/ZGMZwGyGMT/QRbs/ZGMZwGyGMT/QRbs/W6QSwPO5JL+7Ds8/W6QSwPO5JL+7Ds8/W6QSwPO5JL+7Ds8/5LEGwOkoG79nm8U/5LEGwOkoG79nm8U/5LEGwOkoG79nm8U/G/AWwPO5JL+dVbk/G/AWwPO5JL+dVbk/G/AWwPO5JL+dVbk/pP0KwOkoG79I4q8/pP0KwOkoG79I4q8/pP0KwOkoG79I4q8/PSK3vwXbij/MHzo/PSK3vwXbij/MHzo/PSK3vwXbij/MHzo/E3+Yv3F4mT8FKTU/E3+Yv3F4mT8FKTU/E3+Yv3F4mT8FKTU/EJe5vwXbij8N9/o+EJe5vwXbij8N9/o+EJe5vwXbij8N9/o+5/Oav3F4mT96CfE+5/Oav3F4mT96CfE+5/Oav3F4mT96CfE+Jakxv2yF8L5F2Co/Jakxv2yF8L5F2Co/Jakxv2yF8L5F2Co/osXovrQPtr5/4SU/osXovrQPtr5/4SU/osXovrQPtr5/4SU/y5I2v2yF8L76Z9w+y5I2v2yF8L76Z9w+y5I2v2yF8L76Z9w+7pjyvrQPtr5petI+7pjyvrQPtr5petI+7pjyvrQPtr5petI+zBgMwI6slj8dB0Y/zBgMwI6slj8dB0Y/zBgMwI6slj8dB0Y/yfcIwMHGsz+QA0U/yfcIwMHGsz+QA0U/yfcIwMHGsz+QA0U/1CsNwI6slj8r+xA/1CsNwI6slj8r+xA/1CsNwI6slj8r+xA/0woKwMHGsz+Z9w8/0woKwMHGsz+Z9w8/0woKwMHGsz+Z9w8/MSugv6Pudz+2lDI/MSugv6Pudz+2lDI/MSugv6Pudz+2lDI/L+mZv4YRmT8nkTE/L+mZv4YRmT8nkTE/L+mZv4YRmT8nkTE/Q1Giv6Pudz+EEfs+Q1Giv6Pudz+EEfs+Q1Giv6Pudz+EEfs+QA+cv4QRmT9kCvk+QA+cv4QRmT9kCvk+QA+cv4QRmT9kCvk+PbMJwFQ4sz862w8/PbMJwFQ4sz862w8/PbMJwFQ4sz862w8/deX7v7wLoT9UDAw/deX7v7wLoT9UDAw/deX7v7wLoT9UDAw/NaAIwFI4sz8v50Q/NaAIwFI4sz8v50Q/NaAIwFI4sz8v50Q/ZL/5v7wLoT9HGEE/ZL/5v7wLoT9HGEE/ZL/5v7wLoT9HGEE/gYEvwDbsIz9wGxw/gYEvwDbsIz9wGxw/gYEvwDbsIz9wGxw//sAjwAkm/z6JTBg//sAjwAkm/z6JTBg//sAjwAkm/z6JTBg/d24uwDbsIz9jJ1E/d24uwDbsIz9jJ1E/d24uwDbsIz9jJ1E/9a0iwAkm/z58WE0/9a0iwAkm/z58WE0/9a0iwAkm/z58WE0/VSUswGb1Jz8eJ00/VSUswGb1Jz8eJ00/VSUswGb1Jz8eJ00/FVcfwGyGMT/PAEk/FVcfwGyGMT/PAEk/FVcfwGyGMT/PAEk/xxYtwGb1Jz+blR4/xxYtwGb1Jz+blR4/xxYtwGb1Jz+blR4/iEggwGyGMT9Lbxo/iEggwGyGMT9Lbxo/iEggwGyGMT9Lbxo/uLYcwPO5JL/uJkg/uLYcwPO5JL/uJkg/uLYcwPO5JL/uJkg/d+gPwOkoG7+dAEQ/d+gPwOkoG7+dAEQ/d+gPwOkoG7+dAEQ/K6gdwPO5JL9plRk/K6gdwPO5JL9plRk/K6gdwPO5JL9plRk/6dkQwOkoG78ZbxU/6dkQwOkoG78ZbxU/6dkQwOkoG78ZbxU/ZxC1vwXbij95yBg+ZxC1vwXbij95yBg+ZxC1vwXbij95yBg+kWOWv3F4mT8beCg+kWOWv3F4mT8beCg+kWOWv3F4mT8beCg+oh+zvwXbij9ZKrS9oh+zvwXbij9ZKrS9oh+zvwXbij9ZKrS9zXKUv3F4mT8jy5S9zXKUv3F4mT8jy5S9zXKUv3F4mT8jy5S98Ektv2yF8L7LEEk+8Ektv2yF8L7LEEk+8Ektv2yF8L7LEEk+jeDfvrQPtr5twFg+jeDfvrQPtr5twFg+jeDfvrQPtr5twFg+aGgpv2yF8L6LMye9aGgpv2yF8L6LMye9aGgpv2yF8L6LMye9fR3YvrQPtr4l6tC8fR3YvrQPtr4l6tC8fR3YvrQPtr4l6tC87fsKwI6slj8R66897fsKwI6slj8R66897fsKwI6slj8R668979kHwMHGsz+RU7Y979kHwMHGsz+RU7Y979kHwMHGsz+RU7Y9pyIKwI6slj97+vi9pyIKwI6slj97+vi9pyIKwI6slj97+vi9qAAHwMHGsz8LkvK9qAAHwMHGsz8LkvK9qAAHwMHGsz8LkvK9k8udv6Pudz82aRU+k8udv6Pudz82aRU+k8udv6Pudz82aRU+l4eXv4YRmT9xnRg+l4eXv4YRmT9xnRg+l4eXv4YRmT9xnRg+BRmcv6Pudz8/Jny9BRmcv6Pudz8/Jny9BRmcv6Pudz8/Jny9CdWVv4QRmT9fVW+9CdWVv4QRmT9fVW+9CdWVv4QRmT9fVW+996gGwFQ4sz+b3vG996gGwFQ4sz+b3vG996gGwFQ4sz+b3vG9fcn1v7wLoT9Hzdm9fcn1v7wLoT9Hzdm9fcn1v7wLoT9Hzdm9P4IHwFI4sz/3Brc9P4IHwFI4sz/3Brc9P4IHwFI4sz/3Brc9Cnz3v7wLoT9FGM89Cnz3v7wLoT9FGM89Cnz3v7wLoT9FGM89KYMswDbsIz+9pR++KYMswDbsIz+9pR++KYMswDbsIz+9pR++8b4gwAkm/z4TnRO+8b4gwAkm/z4TnRO+8b4gwAkm/z4TnRO+cFwtwDbsIz83NFM9cFwtwDbsIz83NFM9cFwtwDbsIz83NFM9N5ghwAkm/z5oq4E9N5ghwAkm/z5oq4E9N5ghwAkm/z5oq4E9f/QqwGb1Jz/a8Sg9f/QqwGb1Jz/a8Sg9f/QqwGb1Jz/a8Sg9NSIewGyGMT9XZV09NSIewGyGMT9XZV09NSIewGyGMT9XZV09wjUqwGb1Jz9iRBC+wjUqwGb1Jz9iRBC+wjUqwGb1Jz9iRBC+d2MdwGyGMT+HJwO+d2MdwGyGMT+HJwO+d2MdwGyGMT+HJwO+BIEbwPO5JL88J2g9BIEbwPO5JL88J2g9BIEbwPO5JL88J2g9uK4OwOkoG79hTY49uK4OwOkoG79hTY49uK4OwOkoG79hTY49RsIawPO5JL8NdwC+RsIawPO5JL8NdwC+RsIawPO5JL8NdwC++u8NwOkoG79ftOa9+u8NwOkoG79ftOa9+u8NwOkoG79ftOa9ofCqvwXbij9txb++ofCqvwXbij9txb++ofCqvwXbij9txb++kVKNv3F4mT854Z6+kVKNv3F4mT854Z6+kVKNv3F4mT854Z6+U82ivwXbij8ughq/U82ivwXbij8ughq/U82ivwXbij8ughq/Qy+Fv3F4mT8WEAq/Qy+Fv3F4mT8WEAq/Qy+Fv3F4mT8WEAq/RY0fv2yF8L44DzW+RY0fv2yF8L44DzW+RY0fv2yF8L44DzW+SqLIvrQPtr6bjea9SqLIvrQPtr6bjea9SqLIvrQPtr6bjea9rUYPv2yF8L6Rxs++rUYPv2yF8L6Rxs++rUYPv2yF8L6Rxs++FhWovrQPtr5e4q6+FhWovrQPtr5e4q6+FhWovrQPtr5e4q6+JQ4EwI6slj8Ykxe/JQ4EwI6slj8Ykxe/JQ4EwI6slj8Ykxe/zQcBwMHGsz8lNxS/zQcBwMHGsz8lNxS/zQcBwMHGsz8lNxS/9H4AwI6slj/32kq/9H4AwI6slj/32kq/9H4AwI6slj/32kq/OfH6v8HGsz8Hf0e/OfH6v8HGsz8Hf0e/OfH6v8HGsz8Hf0e/xRSUv6Pudz8rS66+xRSUv6Pudz8rS66+xRSUv6Pudz8rS66+GAiOv4YRmT9Kk6e+GAiOv4YRmT9Kk6e+GAiOv4YRmT9Kk6e+Y/aMv6Pudz92bQq/Y/aMv6Pudz92bQq/Y/aMv6Pudz92bQq/temGv4QRmT+GEQe/temGv4QRmT+GEQe/temGv4QRmT+GEQe/4kf6v1Q4sz/+IEe/4kf6v1Q4sz/+IEe/4kf6v1Q4sz/+IEe/K4/jv7wLoT8kgzq/K4/jv7wLoT8kgzq/K4/jv7wLoT8kgzq/I7MAwFI4sz8e2RO/I7MAwFI4sz8e2RO/I7MAwFI4sz8e2RO/ja3qv7wLoT9EOwe/ja3qv7wLoT9EOwe/ja3qv7wLoT9EOwe/BLAhwDbsIz9St2+/BLAhwDbsIz9St2+/BLAhwDbsIz9St2+/qFMWwAkm/z54GWO/qFMWwAkm/z54GWO/qFMWwAkm/z54GWO/NT8lwDbsIz9xbzy/NT8lwDbsIz9xbzy/NT8lwDbsIz9xbzy/2uIZwAkm/z6Y0S+/2uIZwAkm/z6Y0S+/2uIZwAkm/z6Y0S+/rMEiwGb1Jz/WCj2/rMEiwGb1Jz/WCj2/rMEiwGb1Jz/WCj2/j2AWwGyGMT9nSy+/j2AWwGyGMT9nSy+/j2AWwGyGMT9nSy+/w6EfwGb1Jz98D2q/w6EfwGb1Jz98D2q/w6EfwGb1Jz98D2q/pUATwGyGMT8OUFy/pUATwGyGMT8OUFy/pUATwGyGMT8OUFy/lNYTwPO5JL+UeSy/lNYTwPO5JL+UeSy/lNYTwPO5JL+UeSy/dXUHwOkoG78iuh6/dXUHwOkoG78iuh6/dXUHwOkoG78iuh6/qrYQwPO5JL85flm/qrYQwPO5JL85flm/qrYQwPO5JL85flm/i1UEwOkoG7/Ivku/i1UEwOkoG7/Ivku/i1UEwOkoG7/Ivku/irGqPwXbij8FXqI/irGqPwXbij8FXqI/irGqPwXbij8FXqI/ORyOP3F4mT/sD5c/ORyOP3F4mT/sD5c/ORyOP3F4mT/sD5c/veG1PwXbij9MFIY/veG1PwXbij9MFIY/veG1PwXbij9MFIY/bEyZP3F4mT9jjHU/bEyZP3F4mT9jjHU/bEyZP3F4mT9jjHU/6WwlP2yF8L6qI38/6WwlP2yF8L6qI38/6WwlP2yF8L6qI38/ioTYPrQPtr54h2g/ioTYPrQPtr54h2g/ioTYPrQPtr54h2g/T807P2yF8L43kEY/T807P2yF8L43kEY/T807P2yF8L43kEY/raICP7QPtr4D9C8/raICP7QPtr4D9C8/raICP7QPtr4D9C8/z+YCQI6slj+TWsQ/z+YCQI6slj+TWsQ/z+YCQI6slj+TWsQ/A/f/P8HGsz9xC8I/A/f/P8HGsz9xC8I/A/f/P8HGsz9xC8I/jMsHQI6slj/Wm6s/jMsHQI6slj/Wm6s/jMsHQI6slj/Wm6s/PuAEQMHGsz+yTKk/PuAEQMHGsz+yTKk/PuAEQMHGsz+yTKk/R9OVP6Pudz+mEJg/R9OVP6Pudz+mEJg/R9OVP6Pudz+mEJg/rfyPP4YRmT+EwZU/rfyPP4YRmT+EwZU/rfyPP4YRmT+EwZU/wZyfP6Pudz/So34/wZyfP6Pudz/So34/wZyfP6Pudz/So34/J8aZP4QRmT+MBXo/J8aZP4QRmT+MBXo/J8aZP4QRmT+MBXo/iI4EQFQ4sz8QDKk/iI4EQFQ4sz8QDKk/iI4EQFQ4sz8QDKk/dS/zP7wLoT/OX6A/dS/zP7wLoT/OX6A/dS/zP7wLoT/OX6A/mVP/P1I4sz/PysE/mVP/P1I4sz/PysE/mVP/P1I4sz/PysE/+2XpP7wLoT+MHrk/+2XpP7wLoT+MHrk/+2XpP7wLoT+MHrk/6tMnQDbsIz+L8sQ/6tMnQDbsIz+L8sQ/6tMnQDbsIz+L8sQ/HN0cQAkm/z5JRrw/HN0cQAkm/z5JRrw/HN0cQAkm/z5JRrw/Le8iQDbsIz9Jsd0/Le8iQDbsIz9Jsd0/Le8iQDbsIz9Jsd0/YPgXQAkm/z4HBdU/YPgXQAkm/z4HBdU/YPgXQAkm/z4HBdU/GQohQGb1Jz9Bcto/GQohQGb1Jz9Bcto/GQohQGb1Jz9Bcto/pBcVQGyGMT/v/tA/pBcVQGyGMT/v/tA/pBcVQGyGMT/v/tA/2FUlQGb1Jz8jucQ/2FUlQGb1Jz8jucQ/2FUlQGb1Jz8jucQ/ZGMZQGyGMT/QRbs/ZGMZQGyGMT/QRbs/ZGMZQGyGMT/QRbs/W6QSQPO5JL+7Ds8/W6QSQPO5JL+7Ds8/W6QSQPO5JL+7Ds8/5LEGQOkoG79nm8U/5LEGQOkoG79nm8U/5LEGQOkoG79nm8U/G/AWQPO5JL+dVbk/G/AWQPO5JL+dVbk/G/AWQPO5JL+dVbk/pP0KQOkoG79I4q8/pP0KQOkoG79I4q8/pP0KQOkoG79I4q8/PSK3PwXbij/MHzo/PSK3PwXbij/MHzo/PSK3PwXbij/MHzo/E3+YP3F4mT8FKTU/E3+YP3F4mT8FKTU/E3+YP3F4mT8FKTU/EJe5PwXbij8N9/o+EJe5PwXbij8N9/o+EJe5PwXbij8N9/o+5/OaP3F4mT96CfE+5/OaP3F4mT96CfE+5/OaP3F4mT96CfE+JakxP2yF8L5F2Co/JakxP2yF8L5F2Co/JakxP2yF8L5F2Co/osXoPrQPtr5/4SU/osXoPrQPtr5/4SU/osXoPrQPtr5/4SU/y5I2P2yF8L76Z9w+y5I2P2yF8L76Z9w+y5I2P2yF8L76Z9w+7pjyPrQPtr5petI+7pjyPrQPtr5petI+7pjyPrQPtr5petI+zBgMQI6slj8dB0Y/zBgMQI6slj8dB0Y/zBgMQI6slj8dB0Y/yfcIQMHGsz+QA0U/yfcIQMHGsz+QA0U/yfcIQMHGsz+QA0U/1CsNQI6slj8r+xA/1CsNQI6slj8r+xA/1CsNQI6slj8r+xA/0woKQMHGsz+Z9w8/0woKQMHGsz+Z9w8/0woKQMHGsz+Z9w8/MSugP6Pudz+2lDI/MSugP6Pudz+2lDI/MSugP6Pudz+2lDI/L+mZP4YRmT8nkTE/L+mZP4YRmT8nkTE/L+mZP4YRmT8nkTE/Q1GiP6Pudz+EEfs+Q1GiP6Pudz+EEfs+Q1GiP6Pudz+EEfs+QA+cP4QRmT9kCvk+QA+cP4QRmT9kCvk+QA+cP4QRmT9kCvk+PbMJQFQ4sz862w8/PbMJQFQ4sz862w8/PbMJQFQ4sz862w8/deX7P7wLoT9UDAw/deX7P7wLoT9UDAw/deX7P7wLoT9UDAw/NaAIQFI4sz8v50Q/NaAIQFI4sz8v50Q/NaAIQFI4sz8v50Q/ZL/5P7wLoT9HGEE/ZL/5P7wLoT9HGEE/ZL/5P7wLoT9HGEE/gYEvQDbsIz9wGxw/gYEvQDbsIz9wGxw/gYEvQDbsIz9wGxw//sAjQAkm/z6JTBg//sAjQAkm/z6JTBg//sAjQAkm/z6JTBg/d24uQDbsIz9jJ1E/d24uQDbsIz9jJ1E/d24uQDbsIz9jJ1E/9a0iQAkm/z58WE0/9a0iQAkm/z58WE0/9a0iQAkm/z58WE0/VSUsQGb1Jz8eJ00/VSUsQGb1Jz8eJ00/VSUsQGb1Jz8eJ00/FVcfQGyGMT/PAEk/FVcfQGyGMT/PAEk/FVcfQGyGMT/PAEk/xxYtQGb1Jz+blR4/xxYtQGb1Jz+blR4/xxYtQGb1Jz+blR4/iEggQGyGMT9Lbxo/iEggQGyGMT9Lbxo/iEggQGyGMT9Lbxo/uLYcQPO5JL/uJkg/uLYcQPO5JL/uJkg/uLYcQPO5JL/uJkg/d+gPQOkoG7+dAEQ/d+gPQOkoG7+dAEQ/d+gPQOkoG7+dAEQ/K6gdQPO5JL9plRk/K6gdQPO5JL9plRk/K6gdQPO5JL9plRk/6dkQQOkoG78ZbxU/6dkQQOkoG78ZbxU/6dkQQOkoG78ZbxU/ZxC1PwXbij95yBg+ZxC1PwXbij95yBg+ZxC1PwXbij95yBg+kWOWP3F4mT8beCg+kWOWP3F4mT8beCg+kWOWP3F4mT8beCg+oh+zPwXbij9ZKrS9oh+zPwXbij9ZKrS9oh+zPwXbij9ZKrS9zXKUP3F4mT8jy5S9zXKUP3F4mT8jy5S9zXKUP3F4mT8jy5S98EktP2yF8L7LEEk+8EktP2yF8L7LEEk+8EktP2yF8L7LEEk+jeDfPrQPtr5twFg+jeDfPrQPtr5twFg+jeDfPrQPtr5twFg+aGgpP2yF8L6LMye9aGgpP2yF8L6LMye9aGgpP2yF8L6LMye9fR3YPrQPtr4l6tC8fR3YPrQPtr4l6tC8fR3YPrQPtr4l6tC87fsKQI6slj8R66897fsKQI6slj8R66897fsKQI6slj8R668979kHQMHGsz+RU7Y979kHQMHGsz+RU7Y979kHQMHGsz+RU7Y9pyIKQI6slj97+vi9pyIKQI6slj97+vi9pyIKQI6slj97+vi9qAAHQMHGsz8LkvK9qAAHQMHGsz8LkvK9qAAHQMHGsz8LkvK9k8udP6Pudz82aRU+k8udP6Pudz82aRU+k8udP6Pudz82aRU+l4eXP4YRmT9xnRg+l4eXP4YRmT9xnRg+l4eXP4YRmT9xnRg+BRmcP6Pudz8/Jny9BRmcP6Pudz8/Jny9BRmcP6Pudz8/Jny9CdWVP4QRmT9fVW+9CdWVP4QRmT9fVW+9CdWVP4QRmT9fVW+996gGQFQ4sz+b3vG996gGQFQ4sz+b3vG996gGQFQ4sz+b3vG9fcn1P7wLoT9Hzdm9fcn1P7wLoT9Hzdm9fcn1P7wLoT9Hzdm9P4IHQFI4sz/3Brc9P4IHQFI4sz/3Brc9P4IHQFI4sz/3Brc9Cnz3P7wLoT9FGM89Cnz3P7wLoT9FGM89Cnz3P7wLoT9FGM89KYMsQDbsIz+9pR++KYMsQDbsIz+9pR++KYMsQDbsIz+9pR++8b4gQAkm/z4TnRO+8b4gQAkm/z4TnRO+8b4gQAkm/z4TnRO+cFwtQDbsIz83NFM9cFwtQDbsIz83NFM9cFwtQDbsIz83NFM9N5ghQAkm/z5oq4E9N5ghQAkm/z5oq4E9N5ghQAkm/z5oq4E9f/QqQGb1Jz/a8Sg9f/QqQGb1Jz/a8Sg9f/QqQGb1Jz/a8Sg9NSIeQGyGMT9XZV09NSIeQGyGMT9XZV09NSIeQGyGMT9XZV09wjUqQGb1Jz9iRBC+wjUqQGb1Jz9iRBC+wjUqQGb1Jz9iRBC+d2MdQGyGMT+HJwO+d2MdQGyGMT+HJwO+d2MdQGyGMT+HJwO+BIEbQPO5JL88J2g9BIEbQPO5JL88J2g9BIEbQPO5JL88J2g9uK4OQOkoG79hTY49uK4OQOkoG79hTY49uK4OQOkoG79hTY49RsIaQPO5JL8NdwC+RsIaQPO5JL8NdwC+RsIaQPO5JL8NdwC++u8NQOkoG79ftOa9+u8NQOkoG79ftOa9+u8NQOkoG79ftOa9ofCqPwXbij9txb++ofCqPwXbij9txb++ofCqPwXbij9txb++kVKNP3F4mT854Z6+kVKNP3F4mT854Z6+kVKNP3F4mT854Z6+U82iPwXbij8ughq/U82iPwXbij8ughq/U82iPwXbij8ughq/Qy+FP3F4mT8WEAq/Qy+FP3F4mT8WEAq/Qy+FP3F4mT8WEAq/RY0fP2yF8L44DzW+RY0fP2yF8L44DzW+RY0fP2yF8L44DzW+SqLIPrQPtr6bjea9SqLIPrQPtr6bjea9SqLIPrQPtr6bjea9rUYPP2yF8L6Rxs++rUYPP2yF8L6Rxs++rUYPP2yF8L6Rxs++FhWoPrQPtr5e4q6+FhWoPrQPtr5e4q6+FhWoPrQPtr5e4q6+JQ4EQI6slj8Ykxe/JQ4EQI6slj8Ykxe/JQ4EQI6slj8Ykxe/zQcBQMHGsz8lNxS/zQcBQMHGsz8lNxS/zQcBQMHGsz8lNxS/9H4AQI6slj/32kq/9H4AQI6slj/32kq/9H4AQI6slj/32kq/OfH6P8HGsz8Hf0e/OfH6P8HGsz8Hf0e/OfH6P8HGsz8Hf0e/xRSUP6Pudz8rS66+xRSUP6Pudz8rS66+xRSUP6Pudz8rS66+GAiOP4YRmT9Kk6e+GAiOP4YRmT9Kk6e+GAiOP4YRmT9Kk6e+Y/aMP6Pudz92bQq/Y/aMP6Pudz92bQq/Y/aMP6Pudz92bQq/temGP4QRmT+GEQe/temGP4QRmT+GEQe/temGP4QRmT+GEQe/4kf6P1Q4sz/+IEe/4kf6P1Q4sz/+IEe/4kf6P1Q4sz/+IEe/K4/jP7wLoT8kgzq/K4/jP7wLoT8kgzq/K4/jP7wLoT8kgzq/I7MAQFI4sz8e2RO/I7MAQFI4sz8e2RO/I7MAQFI4sz8e2RO/ja3qP7wLoT9EOwe/ja3qP7wLoT9EOwe/ja3qP7wLoT9EOwe/BLAhQDbsIz9St2+/BLAhQDbsIz9St2+/BLAhQDbsIz9St2+/qFMWQAkm/z54GWO/qFMWQAkm/z54GWO/qFMWQAkm/z54GWO/NT8lQDbsIz9xbzy/NT8lQDbsIz9xbzy/NT8lQDbsIz9xbzy/2uIZQAkm/z6Y0S+/2uIZQAkm/z6Y0S+/2uIZQAkm/z6Y0S+/rMEiQGb1Jz/WCj2/rMEiQGb1Jz/WCj2/rMEiQGb1Jz/WCj2/j2AWQGyGMT9nSy+/j2AWQGyGMT9nSy+/j2AWQGyGMT9nSy+/w6EfQGb1Jz98D2q/w6EfQGb1Jz98D2q/w6EfQGb1Jz98D2q/pUATQGyGMT8OUFy/pUATQGyGMT8OUFy/pUATQGyGMT8OUFy/lNYTQPO5JL+UeSy/lNYTQPO5JL+UeSy/lNYTQPO5JL+UeSy/dXUHQOkoG78iuh6/dXUHQOkoG78iuh6/dXUHQOkoG78iuh6/qrYQQPO5JL85flm/qrYQQPO5JL85flm/qrYQQPO5JL85flm/i1UEQOkoG7/Ivku/i1UEQOkoG7/Ivku/i1UEQOkoG7/Ivku/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AAAAANqZfT5oBni/MFKtMUyhfD+3miU+wkJ+P5YW7Dyx5ea96yRGshOCcb9N0qm+AAAAANqZfT5oBni/wkJ+P5YW7Dyx5ea9AAAAAOWZfb5nBng/AAAAAAH2bj+Eq7c+Yil7P6lJRL3E+D8+AAAAAAhSf79zIJW9AAAAAOWZfb5nBng/Yil7P6lJRL3E+D8+wUJ+v5gW7Dyz5ea9AAAAANqZfT5oBni/MFKtMUyhfD+3miU+wUJ+v5gW7Dyz5ea96yRGshOCcb9N0qm+AAAAANqZfT5oBni/Yil7v59JRL3B+D8+AAAAAOWZfb5nBng/AAAAAAH2bj+Eq7c+Yil7v59JRL3B+D8+AAAAAAhSf79zIJW9AAAAAOWZfb5nBng///9/vwAAAAAAAACAAAAAAJuRUL8wcRS/AAAAAC9xFL+akVA///9/vwAAAAAAAACAAAAAAC9xFL+akVA/AAAAAJqRUD8xcRQ///9/vwAAAAAAAACAAAAAAJuRUL8wcRS/AAAAADJxFD+akVC///9/vwAAAAAAAACAAAAAADJxFD+akVC/AAAAAJqRUD8xcRQ/AAAAAJuRUL8wcRS/AAAAAC9xFL+akVA/AACAPwAAAABJAh8zAAAAAC9xFL+akVA/AAAAAJqRUD8xcRQ/AACAPwAAAABJAh8zAAAAAJuRUL8wcRS/AAAAADJxFD+akVC/AACAPwAAAABJAh8zAAAAADJxFD+akVC/AAAAAJqRUD8xcRQ/AACAPwAAAABJAh8zwUJ+v5gW7Dyz5ea9Yil7v59JRL3B+D8+6yRGshOCcb9N0qm+AAAAAAhSf79zIJW9AAAAAAH2bj+Eq7c+MFKtMUyhfD+3miU+Yil7P6lJRL3E+D8+wkJ+P5YW7Dyx5ea9wUJ+v5gW7Dyz5ea9Yil7v59JRL3B+D8+AAAAAAH2bj+Eq7c+MFKtMUyhfD+3miU+6yRGshOCcb9N0qm+AAAAAAhSf79zIJW9Yil7P6lJRL3E+D8+wkJ+P5YW7Dyx5ea9zI54v7hzer0f+2w+Xojas7iBd7/syYK+0Rx1PmD5fb63T3A/zI54v7hzer0f+2w+U1C7tLiBdz/wyYI+0Rx1PmD5fb63T3A/zI54v7hzer0f+2w+6Bx1vjL5fT64T3C/Xojas7iBd7/syYK+zI54v7hzer0f+2w+6Bx1vjL5fT64T3C/U1C7tLiBdz/wyYI+Xojas7iBd7/syYK+0Rx1PmD5fb63T3A/y454Pyx0ej0S+2y+U1C7tLiBdz/wyYI+0Rx1PmD5fb63T3A/y454Pyx0ej0S+2y+6Bx1vjL5fT64T3C/Xojas7iBd7/syYK+y454Pyx0ej0S+2y+6Bx1vjL5fT64T3C/U1C7tLiBdz/wyYI+y454Pyx0ej0S+2y+AACAvwAAAAAAAACAAAAAAJfxZr+Q6tw+AAAAAI7q3D6X8WY/AACAvwAAAAAAAACAAAAAAI7q3D6X8WY/AAAAAJfxZj+Q6ty+AACAvwAAAAAAAACAAAAAAJfxZr+Q6tw+AAAAAI7q3L6X8Wa/AACAvwAAAAAAAACAAAAAAI7q3L6X8Wa/AAAAAJfxZj+Q6ty+AAAAAJfxZr+Q6tw+AAAAAI7q3D6X8WY/AACAPwAAAAAAAACAAAAAAI7q3D6X8WY/AAAAAJfxZj+Q6ty+AACAPwAAAAAAAACAAAAAAJfxZr+Q6tw+AAAAAI7q3L6X8Wa/AACAPwAAAAAAAACAAAAAAI7q3L6X8Wa/AAAAAJfxZj+Q6ty+AACAPwAAAAAAAACAAACAvwAAAAC2lcU0AAAAAJ7xZr916tw+AAAAAJ/q3D6W8WY/AACAvwAAAAC2lcU0AAAAAJ/q3D6W8WY/AAAAAJ7xZj956ty+AACAvwAAAAC2lcU0AAAAAJ7xZr916tw+AAAAAJzq3L6V8Wa/AACAvwAAAAC2lcU0AAAAAJzq3L6V8Wa/AAAAAJ7xZj956ty+AAAAAJ7xZr916tw+AAAAAJ/q3D6W8WY/AACAPwAAAAAAAACAAAAAAJ/q3D6W8WY/AAAAAJ7xZj956ty+AACAPwAAAAAAAACAAAAAAJ7xZr916tw+AAAAAJzq3L6V8Wa/AACAPwAAAAAAAACAAAAAAJzq3L6V8Wa/AAAAAJ7xZj956ty+AACAPwAAAAAAAACA0hx1vmH5fb63T3A/a8D5M7iBd7/tyYK+zI54P7hzer0f+2w+0hx1vmH5fb63T3A/U1C7NLiBdz/vyYI+zI54P7hzer0f+2w+a8D5M7iBd7/tyYK+6Bx1PjT5fT65T3C/zI54P7hzer0f+2w+U1C7NLiBdz/vyYI+6Bx1PjT5fT65T3C/zI54P7hzer0f+2w+zI54vy50ej0T+2y+0hx1vmH5fb63T3A/a8D5M7iBd7/tyYK+zI54vy50ej0T+2y+0hx1vmH5fb63T3A/U1C7NLiBdz/vyYI+zI54vy50ej0T+2y+a8D5M7iBd7/tyYK+6Bx1PjT5fT65T3C/zI54vy50ej0T+2y+U1C7NLiBdz/vyYI+6Bx1PjT5fT65T3C/AAAAAJfxZr+Q6tw+AAAAAI7q3D6X8WY/AACAPwAAAAAwkkyzAAAAAI7q3D6X8WY/AAAAAJfxZj+Q6ty+AACAPwAAAAAwkkyzAAAAAJfxZr+Q6tw+AAAAAI7q3L6X8Wa/AACAPwAAAAAwkkyzAAAAAI7q3L6X8Wa/AAAAAJfxZj+Q6ty+AACAPwAAAAAwkkyzAACAvwAAAAAwkkwyAAAAAJfxZr+Q6tw+AAAAAI7q3D6X8WY/AACAvwAAAAAwkkwyAAAAAI7q3D6X8WY/AAAAAJfxZj+Q6ty+AACAvwAAAAAwkkwyAAAAAJfxZr+Q6tw+AAAAAI7q3L6X8Wa/AACAvwAAAAAwkkwyAAAAAI7q3L6X8Wa/AAAAAJfxZj+Q6ty+AAAAAJ7xZr916tw+AAAAAJ/q3D6W8WY/AACAPwAAAAC2lcU0AAAAAJ/q3D6W8WY/AAAAAJ7xZj956ty+AACAPwAAAAC2lcU0AAAAAJ7xZr916tw+AAAAAJzq3L6V8Wa/AACAPwAAAAC2lcU0AAAAAJzq3L6V8Wa/AAAAAJ7xZj956ty+AACAPwAAAAC2lcU0AACAvwAAAAC2lcWzAAAAAJ7xZr916tw+AAAAAJ/q3D6W8WY/AACAvwAAAAC2lcWzAAAAAJ/q3D6W8WY/AAAAAJ7xZj956ty+AACAvwAAAAC2lcWzAAAAAJ7xZr916tw+AAAAAJzq3L6V8Wa/AACAvwAAAAC2lcWzAAAAAJzq3L6V8Wa/AAAAAJ7xZj956ty+4f1Wv/na275wEKo+8nHMvl8yZz+ruCE+JU+8PgAAAAB4Dm4/8nHMvl8yZz+ruCE+JU+8PgAAAAB4Dm4/4/1WP/3a2z5sEKq+4f1Wv/na275wEKo+8nHMvl8yZz+ruCE+N0+8vnTILrR2Dm6/8nHMvl8yZz+ruCE+N0+8vnTILrR2Dm6/4/1WP/3a2z5sEKq+4f1Wv/na275wEKo+JU+8PgAAAAB4Dm4/CXLMPlcyZ7/LuCG+JU+8PgAAAAB4Dm4/CXLMPlcyZ7/LuCG+4/1WP/3a2z5sEKq+4f1Wv/na275wEKo+N0+8vnTILrR2Dm6/CXLMPlcyZ7/LuCG+N0+8vnTILrR2Dm6/CXLMPlcyZ7/LuCG+4/1WP/3a2z5sEKq+9rNov5nxVz4ME7g+6S9OvjTteb99GaM9Jk+8Pn4p1rR4Dm4/9rNov5nxVz4ME7g+wi9OPjPteT/GGaO9Jk+8Pn4p1rR4Dm4/9rNov5nxVz4ME7g+KE+8vlvGDrV5Dm6/6S9OvjTteb99GaM99rNov5nxVz4ME7g+KE+8vlvGDrV5Dm6/wi9OPjPteT/GGaO96S9OvjTteb99GaM9Jk+8Pn4p1rR4Dm4/+LNoP3HxV74RE7i+wi9OPjPteT/GGaO9Jk+8Pn4p1rR4Dm4/+LNoP3HxV74RE7i+KE+8vlvGDrV5Dm6/6S9OvjTteb99GaM9+LNoP3HxV74RE7i+KE+8vlvGDrV5Dm6/wi9OPjPteT/GGaO9+LNoP3HxV74RE7i+7LU7v9pxHT/qe5Q+KE+8vlfGjjR5Dm6/NlIRPznESj+Z52W+KE+8vlfGjjR5Dm6/NlIRPznESj+Z52W+67U7P9txHb/me5S+7LU7v9pxHT/qe5Q+K0+8PlrGDrR3Dm4/NlIRPznESj+Z52W+K0+8PlrGDrR3Dm4/NlIRPznESj+Z52W+67U7P9txHb/me5S+7LU7v9pxHT/qe5Q+QVIRvyjESr8Z6GU+KE+8vlfGjjR5Dm6/QVIRvyjESr8Z6GU+KE+8vlfGjjR5Dm6/67U7P9txHb/me5S+7LU7v9pxHT/qe5Q+QVIRvyjESr8Z6GU+K0+8PlrGDrR3Dm4/QVIRvyjESr8Z6GU+K0+8PlrGDrR3Dm4/67U7P9txHb/me5S+OAlqv+BmO74BIbk+SEQuviutez+D2Yk9J0+8Phcc7TN4Dm4/SEQuviutez+D2Yk9J0+8Phcc7TN4Dm4/NwlqPwNnOz76ILm+OAlqv+BmO74BIbk+Mk+8vg0c7TN3Dm6/SEQuviutez+D2Yk9Mk+8vg0c7TN3Dm6/SEQuviutez+D2Yk9NwlqPwNnOz76ILm+OAlqv+BmO74BIbk+TUQuPiute7+o2Ym9J0+8Phcc7TN4Dm4/TUQuPiute7+o2Ym9J0+8Phcc7TN4Dm4/NwlqPwNnOz76ILm+OAlqv+BmO74BIbk+Mk+8vg0c7TN3Dm6/TUQuPiute7+o2Ym9Mk+8vg0c7TN3Dm6/TUQuPiute7+o2Ym9NwlqPwNnOz76ILm+FXFmv/za274FWZU9HiPbvl4yZz/gBQ49xV6lPZDTi7T/KX8/HiPbvl4yZz/gBQ49xV6lPZDTi7T/KX8/E3FmP/3a2z7/WJW9FXFmv/za274FWZU9HiPbvl4yZz/gBQ49Dl+lvZDTC7P/KX+/HiPbvl4yZz/gBQ49Dl+lvZDTC7P/KX+/E3FmP/3a2z7/WJW9FXFmv/za274FWZU9xV6lPZDTi7T/KX8/OyPbPlcyZ7+SBQ69xV6lPZDTi7T/KX8/OyPbPlcyZ7+SBQ69E3FmP/3a2z7/WJW9FXFmv/za274FWZU9Dl+lvZDTC7P/KX+/OyPbPlcyZ7+SBQ69Dl+lvZDTC7P/KX+/OyPbPlcyZ7+SBQ69E3FmP/3a2z7/WJW9/mx5v6HxVz6qp6E9DwFdvjTteb9nO488yF6lPYY8E7X/KX8//mx5v6HxVz6qp6E9yF6lPYY8E7X/KX8/DAFdPjPteT/1PI+8/mx5v6HxVz6qp6E9DwFdvjTteb9nO488216lvVfGjjMAKn+//mx5v6HxVz6qp6E9216lvVfGjjMAKn+/DAFdPjPteT/1PI+8DwFdvjTteb9nO488yF6lPYY8E7X/KX8/AW15P27xV74Fp6G9yF6lPYY8E7X/KX8/DAFdPjPteT/1PI+8AW15P27xV74Fp6G9DwFdvjTteb9nO488216lvVfGjjMAKn+/AW15P27xV74Fp6G9216lvVfGjjMAKn+/DAFdPjPteT/1PI+8AW15P27xV74Fp6G9ODNJv9txHT/rZYI9zl6lvU3GDjQAKn+/scMbPzTESj+W5Um9zl6lvU3GDjQAKn+/scMbPzTESj+W5Um9OzNJP9pxHb/8ZIK9ODNJv9txHT/rZYI99F6lPWDGjrQAKn8/scMbPzTESj+W5Um99F6lPWDGjrQAKn8/scMbPzTESj+W5Um9OzNJP9pxHb/8ZIK9ODNJv9txHT/rZYI9vMMbvyjESr+B50k9zl6lvU3GDjQAKn+/vMMbvyjESr+B50k9zl6lvU3GDjQAKn+/OzNJP9pxHb/8ZIK9ODNJv9txHT/rZYI9vMMbvyjESr+B50k99F6lPWDGjrQAKn8/vMMbvyjESr+B50k99F6lPWDGjrQAKn8/OzNJP9pxHb/8ZIK9yNp6v+NmO74klKI9Ico6vi2tez/IHnI8/16lPQAAAAAAKn8/Ico6vi2tez/IHnI8/16lPQAAAAAAKn8/x9p6PwdnOz6hk6K9yNp6v+NmO74klKI9Ico6vi2tez/IHnI8+16lvQsc7TL+KX+/Ico6vi2tez/IHnI8+16lvQsc7TL+KX+/x9p6PwdnOz6hk6K9yNp6v+NmO74klKI9/16lPQAAAAAAKn8/Zso6Piqte7/lHHK8/16lPQAAAAAAKn8/Zso6Piqte7/lHHK8x9p6PwdnOz6hk6K9yNp6v+NmO74klKI9+16lvQsc7TL+KX+/Zso6Piqte7/lHHK8+16lvQsc7TL+KX+/Zso6Piqte7/lHHK8x9p6PwdnOz6hk6K9zblmv/za277z92u9QGjbvl8yZz8oZOC8S6SCvXDILrOIen8/QGjbvl8yZz8oZOC8S6SCvXDILrOIen8/zrlmP//a2z5X92s9zblmv/za277z92u9QGjbvl8yZz8oZOC8J6SCPZLTi7OHen+/QGjbvl8yZz8oZOC8J6SCPZLTi7OHen+/zrlmP//a2z5X92s9zblmv/za277z92u9S6SCvXDILrOIen8/Z2jbPlcyZ79eZOA8S6SCvXDILrOIen8/Z2jbPlcyZ79eZOA8zrlmP//a2z5X92s9zblmv/za277z92u9J6SCPZLTi7OHen+/Z2jbPlcyZ79eZOA8J6SCPZLTi7OHen+/Z2jbPlcyZ79eZOA8zrlmP//a2z5X92s9uLt5v5jxVz5KZ3+9zUZdvjPteb/RTGK8Z6SCvaOp5LSGen8/uLt5v5jxVz5KZ3+9Z6SCvaOp5LSGen8/zEZdPjPteT+NS2I8uLt5v5jxVz5KZ3+9zUZdvjPteb/RTGK8VaSCPRqfoLKGen+/uLt5v5jxVz5KZ3+9VaSCPRqfoLKGen+/zEZdPjPteT+NS2I8zUZdvjPteb/RTGK8Z6SCvaOp5LSGen8/uLt5P3PxV77gaX89Z6SCvaOp5LSGen8/zEZdPjPteT+NS2I8uLt5P3PxV77gaX89zUZdvjPteb/RTGK8VaSCPRqfoLKGen+/uLt5P3PxV77gaX89VaSCPRqfoLKGen+/zEZdPjPteT+NS2I8uLt5P3PxV77gaX89t3JJv9pxHT9FB069ZaSCPR6fIDSGen+/2fQbPzPESj+vgB89ZaSCPR6fIDSGen+/2fQbPzPESj+vgB89t3JJP9xxHb8KBk49t3JJv9pxHT9FB069PKSCvW/av7SIen8/2fQbPzPESj+vgB89PKSCvW/av7SIen8/2fQbPzPESj+vgB89t3JJP9xxHb8KBk49t3JJv9pxHT9FB0694PQbvyzESr+zgB+9ZaSCPR6fIDSGen+/4PQbvyzESr+zgB+9ZaSCPR6fIDSGen+/t3JJP9xxHb8KBk49t3JJv9pxHT9FB0694PQbvyzESr+zgB+9PKSCvW/av7SIen8/4PQbvyzESr+zgB+9PKSCvW/av7SIen8/t3JJP9xxHb8KBk498yl7v+tmO76hb4C9HQU7vi2tez/NRD+8SqSCvT8DI7OHen8/HQU7vi2tez/NRD+8SqSCvT8DI7OHen8/8yl7P/9mOz6fb4A98yl7v+tmO76hb4C9HQU7vi2tez/NRD+8JaSCPYIxFDOGen+/HQU7vi2tez/NRD+8JaSCPYIxFDOGen+/8yl7P/9mOz6fb4A98yl7v+tmO76hb4C9SqSCvT8DI7OHen8/RAU7Piqte7+HRT88SqSCvT8DI7OHen8/RAU7Piqte7+HRT888yl7P/9mOz6fb4A98yl7v+tmO76hb4C9JaSCPYIxFDOGen+/RAU7Piqte7+HRT88JaSCPYIxFDOGen+/RAU7Piqte7+HRT888yl7P/9mOz6fb4A9NMVev/ja275CZXe+oNfTvlsyZz9tQuu98/eIvpDTi7Ntq3Y/oNfTvlsyZz9tQuu98/eIvpDTi7Ntq3Y/MsVeP//a2z5QZXc+NMVev/ja275CZXe+oNfTvlsyZz9tQuu95/eIPli9UbNwq3a/oNfTvlsyZz9tQuu95/eIPli9UbNwq3a/MsVeP//a2z5QZXc+NMVev/ja275CZXe+8/eIvpDTi7Ntq3Y/s9fTPlYyZ79kQus98/eIvpDTi7Ntq3Y/s9fTPlYyZ79kQus9MsVeP//a2z5QZXc+NMVev/ja275CZXe+5/eIPli9UbNwq3a/s9fTPlYyZ79kQus95/eIPli9UbNwq3a/s9fTPlYyZ79kQus9MsVeP//a2z5QZXc+UR9xv7XxVz5r44W+8veIvnop1rRsq3Y/paVVvjPteb+YQ229UR9xv7XxVz5r44W+8veIvnop1rRsq3Y/paVVPjTteT/yQm09UR9xv7XxVz5r44W+paVVvjPteb+YQ2297/eIPlfGjrNtq3a/UR9xv7XxVz5r44W+paVVPjTteT/yQm097/eIPlfGjrNtq3a/8veIvnop1rRsq3Y/paVVvjPteb+YQ229Vx9xP2rxV75o44U+8veIvnop1rRsq3Y/paVVPjTteT/yQm09Vx9xP2rxV75o44U+paVVvjPteb+YQ2297/eIPlfGjrNtq3a/Vx9xP2rxV75o44U+paVVPjTteT/yQm097/eIPlfGjrNtq3a/Vx9xP2rxV75o44U+ioBCv9pxHT/bAFi++feIPoIp1jRsq3a/N5QWPzDESj/LOSc++feIPoIp1jRsq3a/N5QWPzDESj/LOSc+i4BCP9xxHb+4AFg+ioBCv9pxHT/bAFi+7feIvoIp1rRuq3Y/N5QWPzDESj/LOSc+7feIvoIp1rRuq3Y/N5QWPzDESj/LOSc+i4BCP9xxHb+4AFg+ioBCv9pxHT/bAFi+QZQWvyvESr+oOSe++feIPoIp1jRsq3a/QZQWvyvESr+oOSe++feIPoIp1jRsq3a/i4BCP9xxHb+4AFg+ioBCv9pxHT/bAFi+QZQWvyvESr+oOSe+7feIvoIp1rRuq3Y/QZQWvyvESr+oOSe+7feIvoIp1rRuq3Y/i4BCP9xxHb+4AFg+74Byv+ZmO77Bp4a+8/eIvhkc7bNtq3Y/T5I0vi2tez9HiEi98/eIvhkc7bNtq3Y/T5I0vi2tez9HiEi974ByP/xmOz6/p4Y+74Byv+ZmO77Bp4a+T5I0vi2tez9HiEi94/eIPg0cbbNuq3a/T5I0vi2tez9HiEi94/eIPg0cbbNuq3a/74ByP/xmOz6/p4Y+74Byv+ZmO77Bp4a+8/eIvhkc7bNtq3Y/epI0Piqte7+giEg98/eIvhkc7bNtq3Y/epI0Piqte7+giEg974ByP/xmOz6/p4Y+74Byv+ZmO77Bp4a+epI0Piqte7+giEg94/eIPg0cbbNuq3a/epI0Piqte7+giEg94/eIPg0cbbNuq3a/74ByP/xmOz6/p4Y+JE+8vgAAAAB4Dm4/8nHMPl8yZz+ruCE+4f1WP/ra275wEKo+4/1Wv/3a2z5sEKq+JE+8vgAAAAB4Dm4/8nHMPl8yZz+ruCE+N0+8PpHTC7R2Dm6/8nHMPl8yZz+ruCE+4f1WP/ra275wEKo+4/1Wv/3a2z5sEKq+N0+8PpHTC7R2Dm6/8nHMPl8yZz+ruCE+C3LMvlcyZ7/LuCG+JE+8vgAAAAB4Dm4/4f1WP/ra275wEKo+4/1Wv/3a2z5sEKq+C3LMvlcyZ7/LuCG+JE+8vgAAAAB4Dm4/C3LMvlcyZ7/LuCG+N0+8PpHTC7R2Dm6/4f1WP/ra275wEKo+4/1Wv/3a2z5sEKq+C3LMvlcyZ7/LuCG+N0+8PpHTC7R2Dm6/I0+8vlTGDrV4Dm4/6S9OPjLteb99GaM99rNoP5vxVz4JE7g+I0+8vlTGDrV4Dm4/xS9OvjTteT/FGaO99rNoP5vxVz4JE7g+6S9OPjLteb99GaM9JU+8PlnGDrV4Dm6/9rNoP5vxVz4JE7g+xS9OvjTteT/FGaO9JU+8PlnGDrV4Dm6/9rNoP5vxVz4JE7g+9rNov23xV74UE7i+I0+8vlTGDrV4Dm4/6S9OPjLteb99GaM99rNov23xV74UE7i+I0+8vlTGDrV4Dm4/xS9OvjTteT/FGaO99rNov23xV74UE7i+6S9OPjLteb99GaM9JU+8PlnGDrV4Dm6/9rNov23xV74UE7i+xS9OvjTteT/FGaO9JU+8PlnGDrV4Dm6/NVIRvzvESj+R52W+Jk+8PlbGjjR6Dm6/67U7P9pxHT/pe5Q+67U7v9txHb/me5S+NVIRvzvESj+R52W+Jk+8PlbGjjR6Dm6/NVIRvzvESj+R52W+K0+8vlrGjrR3Dm4/67U7P9pxHT/pe5Q+67U7v9txHb/me5S+NVIRvzvESj+R52W+K0+8vlrGjrR3Dm4/Jk+8PlbGjjR6Dm6/RFIRPyXESr8c6GU+67U7P9pxHT/pe5Q+67U7v9txHb/me5S+Jk+8PlbGjjR6Dm6/RFIRPyXESr8c6GU+K0+8vlrGjrR3Dm4/RFIRPyXESr8c6GU+67U7P9pxHT/pe5Q+67U7v9txHb/me5S+K0+8vlrGjrR3Dm4/RFIRPyXESr8c6GU+J0+8vhcc7TN4Dm4/TEQuPiutez+C2Yk9OAlqP+VmO74BIbk+OAlqvwFnOz76ILm+J0+8vhcc7TN4Dm4/TEQuPiutez+C2Yk9TEQuPiutez+C2Yk9LU+8Pggc7TN3Dm6/OAlqP+VmO74BIbk+OAlqvwFnOz76ILm+TEQuPiutez+C2Yk9LU+8Pggc7TN3Dm6/J0+8vhcc7TN4Dm4/TUQuviute7+o2Ym9OAlqP+VmO74BIbk+OAlqvwFnOz76ILm+J0+8vhcc7TN4Dm4/TUQuviute7+o2Ym9TUQuviute7+o2Ym9LU+8Pggc7TN3Dm6/OAlqP+VmO74BIbk+OAlqvwFnOz76ILm+TUQuviute7+o2Ym9LU+8Pggc7TN3Dm6/xV6lvZDTi7T/KX8/GiPbPl0yZz/gBQ49FXFmP/ra274FWZU9E3Fmv/3a2z7/WJW9xV6lvZDTi7T/KX8/GiPbPl0yZz/gBQ49Dl+lPZDTi7P/KX+/GiPbPl0yZz/gBQ49FXFmP/ra274FWZU9E3Fmv/3a2z7/WJW9Dl+lPZDTi7P/KX+/GiPbPl0yZz/gBQ49PCPbvlcyZ7+SBQ69xV6lvZDTi7T/KX8/FXFmP/ra274FWZU9E3Fmv/3a2z7/WJW9PCPbvlcyZ7+SBQ69xV6lvZDTi7T/KX8/PCPbvlcyZ7+SBQ69Dl+lPZDTi7P/KX+/FXFmP/ra274FWZU9E3Fmv/3a2z7/WJW9PCPbvlcyZ7+SBQ69Dl+lPZDTi7P/KX+/xl6lvVPGDrX/KX8/EgFdPjPteb9nO488/Wx5P6PxVz6rp6E9CAFdvjPteT/0PI+8xl6lvVPGDrX/KX8//Wx5P6PxVz6rp6E93V6lPVfGDjQAKn+/EgFdPjPteb9nO488/Wx5P6PxVz6rp6E9CAFdvjPteT/0PI+83V6lPVfGDjQAKn+//Wx5P6PxVz6rp6E9Am15v27xV77rpqG9xl6lvVPGDrX/KX8/EgFdPjPteb9nO488Am15v27xV77rpqG9CAFdvjPteT/0PI+8xl6lvVPGDrX/KX8/Am15v27xV77rpqG93V6lPVfGDjQAKn+/EgFdPjPteb9nO488Am15v27xV77rpqG9CAFdvjPteT/0PI+83V6lPVfGDjQAKn+/scMbvzPESj985Um9zl6lPU3GDjQAKn+/ODNJP9txHT/qZYI9OjNJv9pxHb/8ZIK9scMbvzPESj985Um9zl6lPU3GDjQAKn+/scMbvzPESj985Um99l6lvWHGjrQAKn8/ODNJP9txHT/qZYI9OjNJv9pxHb/8ZIK9scMbvzPESj985Um99l6lvWHGjrQAKn8/zl6lPU3GDjQAKn+/u8MbPyrESr+B50k9ODNJP9txHT/qZYI9OjNJv9pxHb/8ZIK9zl6lPU3GDjQAKn+/u8MbPyrESr+B50k99l6lvWHGjrQAKn8/u8MbPyrESr+B50k9ODNJP9txHT/qZYI9OjNJv9pxHb/8ZIK99l6lvWHGjrQAKn8/u8MbPyrESr+B50k9BF+lvQAAAAAAKn8/GMo6Pi2tez/FHnI8yNp6P+RmO74klKI9x9p6vwVnOz6hk6K9BF+lvQAAAAAAKn8/GMo6Pi2tez/FHnI8+l6lPQocbTP/KX+/GMo6Pi2tez/FHnI8yNp6P+RmO74klKI9x9p6vwVnOz6hk6K9+l6lPQocbTP/KX+/GMo6Pi2tez/FHnI8aso6viqte7/lHHK8BF+lvQAAAAAAKn8/yNp6P+RmO74klKI9x9p6vwVnOz6hk6K9aso6viqte7/lHHK8BF+lvQAAAAAAKn8/aso6viqte7/lHHK8+l6lPQocbTP/KX+/yNp6P+RmO74klKI9x9p6vwVnOz6hk6K9aso6viqte7/lHHK8+l6lPQocbTP/KX+/SaSCPYvTC7OGen8/QGjbPl8yZz8oZOC8zblmP/za277z92u9zrlmv//a2z5X92s9SaSCPYvTC7OGen8/QGjbPl8yZz8oZOC8KKSCvZPTi7OIen+/QGjbPl8yZz8oZOC8zblmP/za277z92u9zrlmv//a2z5X92s9KKSCvZPTi7OIen+/QGjbPl8yZz8oZOC8Z2jbvlYyZ79eZOA8SaSCPYvTC7OGen8/zblmP/za277z92u9zrlmv//a2z5X92s9Z2jbvlYyZ79eZOA8SaSCPYvTC7OGen8/Z2jbvlYyZ79eZOA8KKSCvZPTi7OIen+/zblmP/za277z92u9zrlmv//a2z5X92s9Z2jbvlYyZ79eZOA8KKSCvZPTi7OIen+/Z6SCPUgC6LSGen8/zUZdPjPteb/RTGK8ubt5P5bxVz5KZ3+9zkZdvjPteT+NS2I8Z6SCPUgC6LSGen8/ubt5P5bxVz5KZ3+9VqSCvVDGDrOGen+/zUZdPjPteb/RTGK8ubt5P5bxVz5KZ3+9zkZdvjPteT+NS2I8VqSCvVDGDrOGen+/ubt5P5bxVz5KZ3+9uLt5v3HxV76aaX89Z6SCPUgC6LSGen8/zUZdPjPteb/RTGK8uLt5v3HxV76aaX89zkZdvjPteT+NS2I8Z6SCPUgC6LSGen8/uLt5v3HxV76aaX89VqSCvVDGDrOGen+/zUZdPjPteb/RTGK8uLt5v3HxV76aaX89zkZdvjPteT+NS2I8VqSCvVDGDrOGen+/1/QbvzXESj+egB89ZKSCveh3MjSHen+/uHJJP9txHT9IB069t3JJv9xxHb8KBk491/QbvzXESj+egB89ZKSCveh3MjSHen+/1/QbvzXESj+egB89OaSCPZ5QxLSHen8/uHJJP9txHT9IB069t3JJv9xxHb8KBk491/QbvzXESj+egB89OaSCPZ5QxLSHen8/ZKSCveh3MjSHen+/4vQbPyzESr+0gB+9uHJJP9txHT9IB069t3JJv9xxHb8KBk49ZKSCveh3MjSHen+/4vQbPyzESr+0gB+9OaSCPZ5QxLSHen8/4vQbPyzESr+0gB+9uHJJP9txHT9IB069t3JJv9xxHb8KBk49OaSCPZ5QxLSHen8/4vQbPyzESr+0gB+9SqSCPT8DI7OHen8/HwU7Piytez/LRD+88yl7P+xmO76hb4C98il7v/9mOz6fb4A9SqSCPT8DI7OHen8/HwU7Piytez/LRD+8IaSCvX8xFDOHen+/HwU7Piytez/LRD+88yl7P+xmO76hb4C98il7v/9mOz6fb4A9IaSCvX8xFDOHen+/HwU7Piytez/LRD+8SAU7viqte7+HRT88SqSCPT8DI7OHen8/8yl7P+xmO76hb4C98il7v/9mOz6fb4A9SAU7viqte7+HRT88SqSCPT8DI7OHen8/SAU7viqte7+HRT88IaSCvX8xFDOHen+/8yl7P+xmO76hb4C98il7v/9mOz6fb4A9SAU7viqte7+HRT88IaSCvX8xFDOHen+/8veIPo/Ti7Ntq3Y/oNfTPlwyZz9tQuu9NMVeP/ra275DZXe+MsVev//a2z5QZXc+8veIPo/Ti7Ntq3Y/oNfTPlwyZz9tQuu95/eIvpDTi7Nvq3a/oNfTPlwyZz9tQuu9NMVeP/ra275DZXe+MsVev//a2z5QZXc+5/eIvpDTi7Nvq3a/oNfTPlwyZz9tQuu9s9fTvlYyZ79iQus98veIPo/Ti7Ntq3Y/NMVeP/ra275DZXe+MsVev//a2z5QZXc+s9fTvlYyZ79iQus98veIPo/Ti7Ntq3Y/s9fTvlYyZ79iQus95/eIvpDTi7Nvq3a/NMVeP/ra275DZXe+MsVev//a2z5QZXc+s9fTvlYyZ79iQus95/eIvpDTi7Nvq3a/paVVPjPteb+YQ2298PeIPngp1rRtq3Y/UR9xP7XxVz5r44W+pKVVvjTteT/xQm098PeIPngp1rRtq3Y/UR9xP7XxVz5r44W+7veIvgAAAABtq3a/paVVPjPteb+YQ229UR9xP7XxVz5r44W+7veIvgAAAABtq3a/pKVVvjTteT/xQm09UR9xP7XxVz5r44W+Vx9xv2nxV75l44U+paVVPjPteb+YQ2298PeIPngp1rRtq3Y/Vx9xv2nxV75l44U+pKVVvjTteT/xQm098PeIPngp1rRtq3Y/Vx9xv2nxV75l44U+7veIvgAAAABtq3a/paVVPjPteb+YQ229Vx9xv2nxV75l44U+7veIvgAAAABtq3a/pKVVvjTteT/xQm09N5QWvzHESj/JOSc+9veIvn4p1jRrq3a/i4BCP9hxHT/ZAFi+ioBCv9txHb+1AFg+N5QWvzHESj/JOSc+9veIvn4p1jRrq3a/N5QWvzHESj/JOSc+7feIPoEp1rRuq3Y/i4BCP9hxHT/ZAFi+ioBCv9txHb+1AFg+N5QWvzHESj/JOSc+7feIPoEp1rRuq3Y/9veIvn4p1jRrq3a/QJQWPyvESr+nOSe+i4BCP9hxHT/ZAFi+ioBCv9txHb+1AFg+9veIvn4p1jRrq3a/QJQWPyvESr+nOSe+7feIPoEp1rRuq3Y/QJQWPyvESr+nOSe+i4BCP9hxHT/ZAFi+ioBCv9txHb+1AFg+7feIPoEp1rRuq3Y/QJQWPyvESr+nOSe+T5I0Piytez9HiEi98/eIPhcc7bNtq3Y/74ByP+ZmO77Ap4a+74Byv/1mOz6/p4Y+T5I0Piytez9HiEi98/eIPhcc7bNtq3Y/4veIvgocbbNuq3a/T5I0Piytez9HiEi974ByP+ZmO77Ap4a+74Byv/1mOz6/p4Y+4veIvgocbbNuq3a/T5I0Piytez9HiEi9gJI0viqte7+fiEg98/eIPhcc7bNtq3Y/74ByP+ZmO77Ap4a+74Byv/1mOz6/p4Y+gJI0viqte7+fiEg98/eIPhcc7bNtq3Y/4veIvgocbbNuq3a/gJI0viqte7+fiEg974ByP+ZmO77Ap4a+74Byv/1mOz6/p4Y+4veIvgocbbNuq3a/gJI0viqte7+fiEg9Uf3fPULt2z5SNAg+HvgXP74BAD1MAog+UDQIPh74Fz97/j8+Qu3bPlH93z1MAog+ev4/Plzwpz5QNAg+Qu3bPr4BAD1C7ds+UDQIPkLt2z4k/4c+XPCnPlH93z1C7ds+mgJwPh74Fz9R/d89XPCnPpoCcD4e+Bc/NR+gPh74Fz/KAQA9HvgXP3v+Pz5c8Kc+mgJwPkLt2z56/j8+Qu3bPpoCcD5C7ds+NR+gPkLt2z6+AQA9Qu3bPiT/hz5C7ds+ogUQP/j3fz7Ti0w/MBqBPvADST94v7Y9G3kSP6oGgT5tACQ/+Pd/Prz+XD94v7Y9R/tPP378pz5b50o/GnbUPvyaST8wGoE+kx0UPxp21D4W/WE/fvynPswdWz8wGoE+0VRFP3i/tj2iBRA/xLUXPvD1MT8wGoE+BloxP3i/tj39Di0/qgaBPm0AJD/EtRc+xb1EPzAagT5H+08/kCnYPmqaMz8adtQ++DozPzAagT6Hais/GnbUPhb9YT+QKdg+HBCgPaABAD21MvA9lPJvPscXYD548Ac+Svr/PKABAD3j/i8+ePAHPrUy8D2U8m8+HBCgPXjwBz6yMvA9ePAHPuP+Lz7wKD89Svr/PHjwBz7yAAA+8Cg/PbIy8D188Ac+Svr/PJTybz7HF2A+8Cg/PRwQoD2gAQA94/4vPvAoPz2/BFA+lPJvPvgL+z2gAQA9Svr/PHjwBz7j/i8+ePAHPhwQoD148Ac+8gAAPnjwBz6/BFA+ePAHPvIAAD548Ac+d4IvP4SbLj53gi8/hJsuPneCLz/AqKo+d4IvP8Coqj5M/04/iOKqPkz/Tj+I4qo+YyxHP4SbLj5jLEc/hJsuPmEsRz98my4+YSxHP3ybLj53gi8/iOKqPneCLz+I4qo+ogUQP8Coqj6iBRA/wKiqPkvWXj+Emy4+S9ZeP4SbLj5tAnI/8My/PWkAbD+gx/48ZgZmP6DH/jxvAHg/8My/PWkAbD/Ax/48ZgZmPwCX/jttAnI/wMf+PG0Ccj8Al/47aQBsP/DMvz1vAHg/wMf+PGkAbD8Al/47aQBsP/DMvz1tAnI/4Mf+PGkAbD+gx/48bwB4P+DH/jxmBmY/oMf+PGkAbD8Al/47dAJ+P+DH/jxtAnI/oMf+PG0Ccj/wzL89bwB4P/DMvz1rAGw/oMf+PGYGZj/wzL89dAJ+P/DMvz0vBXI/PNMPPi8Fcj8g9Rc+qAFmP9j9Lz4vBXI/wDzQPagBZj880w8+LwVyPyg68D2oAGw/PNMPPi8Fcj/Y/S8+LwVyP9j9Lz6oAGw/wDzQPS8Fcj880w8+0xh4Pyg68D3TGHg/IPUXPqgAbD/Y/S8+qABsP8A80D2oAGw/PNMPPi8Fcj9A0w8+qABsPzzTDz7TGHg/2P0vPqgAbD/Y/S8+qAFmP8A80D2oAGw/PNMPPtMYeD9A0w8+qAFmPzzTDz6l/20/sPpnPqX/bT8g+lc+9QBmP8j6Tz6l/20/yPpPPvUAZj/o/Tc+pf9tPwD1Pz5NAGo/rPpnPqX/bT+w+mc+pf9tP8j6Tz5NAGo/yPpPPqX/bT/o/Tc+/f5xPwD1Pz79/nE/IPpXPk0Aaj/I+k8+TQBqP8j6Tz5NAGo/5P03PqX/bT/I+k8+TQBqP7D6Zz79/nE/sPpnPk0Aaj/I+k8+9QBmP8j6Tz5NAGo/6P03Pv3+cT/I+k8+9QBmP7D6Zz5mBmY/oMf+PGkAbD+gx/48bQJyP/DMvz1mBmY/AJf+O2kAbD/Ax/48bwB4P/DMvz1pAGw/8My/PW0Ccj8Al/47bQJyP8DH/jxpAGw/8My/PWkAbD8Al/47bwB4P8DH/jxvAHg/4Mf+PGkAbD+gx/48bQJyP+DH/jx0An4/4Mf+PGkAbD8Al/47ZgZmP6DH/jxvAHg/8My/PW0Ccj/wzL89bQJyP6DH/jx0An4/8My/PWYGZj/wzL89awBsP6DH/jwvBXI/IPUXPqgBZj/Y/S8+LwVyPzzTDz6oAWY/PNMPPi8Fcj8oOvA9LwVyP8A80D0vBXI/2P0vPi8Fcj/Y/S8+qABsPzzTDz4vBXI/PNMPPtMYeD8oOvA9qABsP8A80D2oAGw/wDzQPdMYeD8g9Rc+qABsP9j9Lz6oAGw/PNMPPqgAbD880w8+LwVyP0DTDz6oAWY/wDzQPdMYeD/Y/S8+qABsP9j9Lz6oAWY/PNMPPqgAbD880w8+0xh4P0DTDz6l/20/IPpXPvUAZj/I+k8+pf9tP7D6Zz71AGY/6P03PqX/bT8A9T8+pf9tP8j6Tz6l/20/sPpnPqX/bT/I+k8+TQBqP6z6Zz6l/20/6P03Pv3+cT8A9T8+TQBqP8j6Tz5NAGo/yPpPPv3+cT8g+lc+TQBqP8j6Tz5NAGo/sPpnPk0Aaj/k/Tc+pf9tP8j6Tz71AGY/yPpPPv3+cT+w+mc+TQBqP8j6Tz71AGY/sPpnPk0Aaj/o/Tc+/f5xP8j6Tz7wAh4/G/1rP3r2KT+JBGY//QQMP9gSRD969ik/VAZgP2kFEj/YEkQ/rQoYP9gSRD8z+yM/Gv1rPzP7Iz+JBGY/aQUSPxv9az8z+yM/VAZgP60KGD8a/Ws/8AIeP9gSRD/wAh4/1xJEP/wEDD8b/Ws/fPYpP4oEZj9pBRI/G/1rP3z2KT8b/Ws/rgoYPxv9az8z+yM/1xJEP2kFEj/XEkQ/M/sjP4oEZj+tChg/2BJEPzP7Iz8a/Ws/8AIePxv9az8K+0s/LP9rP6MCRj8sAFI/FQEuPywAUj8K+0s/uxFmPzwSQD8s/2s/fw40P8D2UT+jAkY/LP9rP38OND/A9Ws/PBJAPywAUj+jAkY/uxFmPx4HOj8s/2s/Hgc6Pyz/az+jAkY/LP9rPxUBLj8s/2s/CvtLP0oEYD88EkA/LQBSP38OND/A9Ws/CvtLP7sRZj+ADjQ/wPZRPzwSQD8s/2s/owJGP0oEYD8eBzo/LQBSPx4HOj8sAFI/owJGP7oRZj+MEMw+5QJMP9Hysz7mBGY/2gDkPuYEZj/W/b8+5gRmP9oA5D6iC2A/2gDkPuUCTD8Z/dc+5QJMP4wQzD7mBGY/NCDwPuYEZj/W/b8+5gRmPzQg8D6iC2A/G/3XPuUCTD+MEMw+5gRmPzIg8D6iC2A/0fKzPuUCTD8yIPA++f1ZP9X9vz7lAkw/2gDkPuYEZj8Z/dc+5gRmP9oA5D6iC2A/jBDMPuUCTD/aAOQ++f1ZP9b9vz7lAkw/Gf3XPuYEZj/bDWg/Ce9DP40Abj+zAGY/WBFWP6b0az+NAG4/C/JfPz8KUD+m9Gs/cihcPwnvQz8nA2I/Ce9DP1oRVj+m9Gs/2w1oP7IAZj9zaFw/pvRrP9sNaD8L8l8/JwNiPwnvQz/bDWg/pvRrP40Abj+zAGY/WBFWPwnvQz+NAG4/pvRrPz0KUD8J70M/c2hcP6b0az8nQ2I/pvRrP1gRVj8J70M/2w1oP7MAZj9zKFw/Ce9DP9sNaD+m9Gs/J0NiP6b0az8z+yM/1xJEP3z2KT+KBGY/agUSP9cSRD989ik/VAZgP60KGD/YEkQ/rgoYP9gSRD/wAh4/2BJEPzP7Iz+KBGY//QQMPxv9az8z+yM/VAZgP2kFEj8a/Ws/8AIeP9gSRD8z+yM/G/1rP2kFEj8b/Ws/fPYpP4oEZj+tChg/G/1rP3z2KT8b/Ws/rQoYPxv9az/wAh4/G/1rP/wEDD/XEkQ/M/sjP4oEZj9pBRI/1xJEPzP7Iz8b/Ws/8AIePxv9az8K+0s/LP9rPzwSQD8s/2s/fw40P8D1az8K+0s/uxFmPxYBLj8s/2s/PBJAPyz/az+jAkY/LP9rP6MCRj8s/2s/Hgc6Py0AUj+jAkY/uxFmP38OND/A9lE/Hgc6Pyz/az88EkA/LABSP38OND/B9lE/owJGP7sRZj8VAS4/LQBSPzwSQD8sAFI/owJGP0oEYD+jAkY/LABSPx4HOj8s/2s/CvtLP7sRZj9/DjQ/wPVrPx4HOj8sAFI/CvtLP0oEYD8Z/dc+5gRmP9b9vz7lAkw/NCDwPqILYD/R8rM+5QJMPzIg8D7mBGY/2gDkPuUCTD+MEMw+5gRmP4wQzD7mBGY/2gDkPqILYD/W/b8+5gRmP9oA5D7mBGY/Gf3XPuUCTD8Z/dc+5QJMPzIg8D6iC2A/1f2/PuYEZj8yIPA++P1ZP9Hysz7mBGY/2gDkPuYEZj+MEMw+5QJMP9oA5D6hC2A/jBDMPuUCTD/aAOQ++P1ZP9b9vz7lAkw/Gf3XPuYEZj/bDWg/Cu9DP48Abj+yAGY/WBFWPwnvQz+PAG4/CvJfP3IoXD8K70M/cyhcPwrvQz8nA2I/Cu9DP9sNaD+zAGY/PwpQP6b0az/bDWg/C/JfP1gRVj+m9Gs/JwNiPwrvQz/bDWg/pvRrP1gRVj+m9Gs/jwBuP7QAZj9yaFw/pvRrP48Abj+m9Gs/c2hcP6b0az8nQ2I/pvRrPz0KUD8J70M/2w1oP7QAZj9YEVY/Ce9DP9sNaD+m9Gs/J0NiP6b0az/wAh4/G/1rP3r2KT+KBGY//AQMP9cSRD969ik/VAZgP2kFEj/XEkQ/rQoYP9gSRD8x+yM/G/1rPzP7Iz+KBGY/rQoYP9gSRD8x+yM/VAZgP2kFEj/YEkQ/8AIeP9gSRD/wAh4/1xJEP/0EDD8b/Ws/MfsjPxr9az9pBRI/G/1rPzH7Iz+KBGY/rQoYPxv9az8x+yM/2BJEP60KGD8b/Ws/evYpPxv9az9pBRI/G/1rP3r2KT+KBGY/8AIePxv9az+jAkY/uxFmP6MCRj8sAFI/FgEuPy0AUj+jAkY/LP9rP38OND/A9lE/Hgc6Py0AUj8K+0s/uxFmPzwSQD8sAFI/Hgc6PywAUj8K+0s/LP9rP38OND/A9lE/PBJAPywAUj+jAkY/LP9rPxUBLj8s/2s/CvtLP0oEYD9/DjQ/wPVrPx4HOj8s/2s/CvtLP7sRZj88EkA/LP9rPx4HOj8s/2s/owJGP0oEYD+ADjQ/wPVrPzwSQD8s/2s/owJGP7oRZj8Z/dc+5gRmP9b9vz7mBGY/MiDwPqILYD+MEMw+5gRmPzIg8D7mBGY/Gf3XPuYEZj+MEMw+5gRmP9Hysz7lAkw/2gDkPqILYD/W/b8+5QJMP9oA5D7mBGY/2gDkPuYEZj8Z/dc+5QJMP9oA5D74/Vk/1v2/PuUCTD/aAOQ+oQtgP4wQzD7lAkw/Gf3XPuUCTD+MEMw+5QJMPzIg8D74/Vk/0fKzPuYEZj8yIPA+ogtgP9b9vz7mBGY/2gDkPuUCTD8nA2I/Ce9DP40Abj+yAGY/WBFWP6b0az+NAG4/CvJfPz8KUD+m9Gs/JwNiPwrvQz9zKFw/Ce9DP9sNaD+zAGY/WBFWP6b0az/bDWg/CvJfP3NoXD+m9Gs/2w1oPwrvQz8nQ2I/pvRrP1gRVj8J70M/jQBuP7MAZj89ClA/Ce9DP40Abj+m9Gs/J0NiP6b0az9zaFw/pvRrP1gRVj8K70M/2w1oP7QAZj9zKFw/Cu9DP9sNaD+m9Gs/2w1oP6b0az8z+yM/1xJEP3z2KT+KBGY/rQoYPxv9az989ik/VAZgP2kFEj8a/Ws/rgoYP9gSRD/wAh4/1xJEPzP7Iz+JBGY/aQUSP9gSRD8z+yM/VAZgP/0EDD/YEkQ/8AIeP9gSRD8z+yM/G/1rP60KGD/XEkQ/M/sjPxr9az9pBRI/1xJEPzP7Iz+KBGY/rQoYPxv9az/wAh4/Gv1rP2kFEj8b/Ws/fPYpPxv9az/8BAw/G/1rP3z2KT+KBGY/8AIePxv9az8K+0s/uxFmP38OND/A9Ws/owJGPysAUj8K+0s/SgRgPxYBLj8s/2s/Hgc6PywAUj+jAkY/uxFmPzwSQD8rAFI/Hgc6PywAUj+jAkY/SgRgPzwSQD8rAFI/fw40P8D2UT9/DjQ/wPZRP6MCRj8s/2s/owJGPyz/az8VAS4/LQBSPx4HOj8s/2s/owJGP7sRZj88EkA/LP9rPx4HOj8s/2s/CvtLPyz/az88EkA/LP9rP4AOND/A9Ws/CvtLP7sRZj8b/dc+5gRmP9b9vz7lAkw/MiDwPvn9WT/R8rM+5QJMPzIg8D6iC2A/2gDkPuUCTD+MEMw+5gRmP4wQzD7mBGY/2gDkPvj9WT/W/b8+5gRmP9oA5D6iC2A/G/3XPuUCTD8b/dc+5QJMPzIg8D7mBGY/1f2/PuYEZj8yIPA+ogtgP9Hysz7mBGY/2gDkPuYEZj+MEMw+5QJMP9oA5D7mBGY/jBDMPuUCTD/aAOQ+ogtgP9b9vz7lAkw/G/3XPuYEZj/aDWg/Ce9DP1gRVj8J70M/2w1oPwryXz9yKFw/Ce9DP9sNaD+yAGY/cihcPwrvQz8nA2I/Ce9DP40Abj8K8l8/PwpQP6b0az+NAG4/tABmP1gRVj+m9Gs/JwNiPwrvQz/bDWg/pvRrP1gRVj+m9Gs/jQBuP7QAZj9xaFw/pvRrP40Abj+m9Gs/cmhcP6b0az8nQ2I/pvRrP9sNaD+0AGY/PQpQPwrvQz/bDWg/pvRrP1gRVj8K70M/J0NiP6b0az/9BAw/2BJEP3r2KT+JBGY/8AIePxv9az+tChg/2BJEP2kFEj/YEkQ/evYpP1QGYD9pBRI/G/1rPzP7Iz+JBGY/M/sjPxr9az/wAh4/2BJEP60KGD8a/Ws/M/sjP1QGYD989ik/igRmP/wEDD8b/Ws/8AIeP9cSRD+uChg/G/1rP3z2KT8b/Ws/aQUSPxv9az8z+yM/igRmP2kFEj/XEkQ/M/sjP9cSRD/wAh4/G/1rPzP7Iz8a/Ws/rQoYP9gSRD8VAS4/LABSP6MCRj8sAFI/CvtLPyz/az9/DjQ/wPZRPzwSQD8s/2s/CvtLP7sRZj88EkA/LABSP38OND/A9Ws/owJGPyz/az8eBzo/LP9rPx4HOj8s/2s/owJGP7sRZj8K+0s/SgRgPxUBLj8s/2s/owJGPyz/az8K+0s/uxFmP38OND/A9Ws/PBJAPy0AUj+jAkY/SgRgPzwSQD8s/2s/gA40P8D2UT+jAkY/uhFmPx4HOj8sAFI/Hgc6Py0AUj/aAOQ+5gRmP9Hysz7mBGY/jBDMPuUCTD/aAOQ+5QJMP9oA5D6iC2A/1v2/PuYEZj80IPA+5gRmP4wQzD7mBGY/Gf3XPuUCTD8b/dc+5QJMPzQg8D6iC2A/1v2/PuYEZj/R8rM+5QJMPzIg8D6iC2A/jBDMPuYEZj/aAOQ+5gRmP9X9vz7lAkw/MiDwPvn9WT+MEMw+5QJMP9oA5D6iC2A/Gf3XPuYEZj8Z/dc+5gRmP9b9vz7lAkw/2gDkPvn9WT9YEVY/pvRrP40Abj+zAGY/2w1oPwnvQz9yKFw/Ce9DPz8KUD+m9Gs/jQBuPwvyXz/bDWg/sgBmP1oRVj+m9Gs/JwNiPwnvQz8nA2I/Ce9DP9sNaD8L8l8/c2hcP6b0az9YEVY/Ce9DP40Abj+zAGY/2w1oP6b0az9zaFw/pvRrPz0KUD8J70M/jQBuP6b0az/bDWg/swBmP1gRVj8J70M/J0NiP6b0az8nQ2I/pvRrP9sNaD+m9Gs/cyhcPwnvQz9qBRI/1xJEP3z2KT+KBGY/M/sjP9cSRD+uChg/2BJEP60KGD/YEkQ/fPYpP1QGYD/9BAw/G/1rPzP7Iz+KBGY/8AIeP9gSRD/wAh4/2BJEP2kFEj8a/Ws/M/sjP1QGYD989ik/igRmP2kFEj8b/Ws/M/sjPxv9az+tChg/G/1rP3z2KT8b/Ws/rQoYPxv9az8z+yM/igRmP/wEDD/XEkQ/8AIePxv9az/wAh4/G/1rPzP7Iz8b/Ws/aQUSP9cSRD9/DjQ/wPVrPzwSQD8s/2s/CvtLPyz/az88EkA/LP9rPxYBLj8s/2s/CvtLP7sRZj8eBzo/LQBSP6MCRj8s/2s/owJGPyz/az8eBzo/LP9rP38OND/A9lE/owJGP7sRZj+jAkY/uxFmP38OND/B9lE/PBJAPywAUj+jAkY/SgRgPzwSQD8sAFI/FQEuPy0AUj8K+0s/uxFmPx4HOj8s/2s/owJGPywAUj8K+0s/SgRgPx4HOj8sAFI/fw40P8D1az80IPA+ogtgP9b9vz7lAkw/Gf3XPuYEZj/aAOQ+5QJMPzIg8D7mBGY/0fKzPuUCTD/aAOQ+ogtgP4wQzD7mBGY/jBDMPuYEZj8Z/dc+5QJMP9oA5D7mBGY/1v2/PuYEZj/V/b8+5gRmPzIg8D6iC2A/Gf3XPuUCTD/aAOQ+5gRmP9Hysz7mBGY/MiDwPvj9WT+MEMw+5QJMP9oA5D6hC2A/jBDMPuUCTD8Z/dc+5gRmP9b9vz7lAkw/2gDkPvj9WT9YEVY/Ce9DP48Abj+yAGY/2w1oPwrvQz9zKFw/Cu9DP3IoXD8K70M/jwBuPwryXz8/ClA/pvRrP9sNaD+zAGY/JwNiPwrvQz8nA2I/Cu9DP1gRVj+m9Gs/2w1oPwvyXz+PAG4/tABmP1gRVj+m9Gs/2w1oP6b0az9zaFw/pvRrP48Abj+m9Gs/cmhcP6b0az/bDWg/tABmPz0KUD8J70M/J0NiP6b0az8nQ2I/pvRrP9sNaD+m9Gs/WBFWPwnvQz/8BAw/1xJEP3r2KT+KBGY/8AIePxv9az+tChg/2BJEP2kFEj/XEkQ/evYpP1QGYD+tChg/2BJEPzP7Iz+KBGY/MfsjPxv9az/wAh4/2BJEP2kFEj/YEkQ/MfsjP1QGYD8x+yM/Gv1rP/0EDD8b/Ws/8AIeP9cSRD+tChg/G/1rPzH7Iz+KBGY/aQUSPxv9az969ik/G/1rP60KGD8b/Ws/MfsjP9gSRD/wAh4/G/1rP3r2KT+KBGY/aQUSPxv9az8WAS4/LQBSP6MCRj8sAFI/owJGP7sRZj8eBzo/LQBSP38OND/A9lE/owJGPyz/az8eBzo/LABSPzwSQD8sAFI/CvtLP7sRZj88EkA/LABSP38OND/A9lE/CvtLPyz/az8K+0s/SgRgPxUBLj8s/2s/owJGPyz/az8K+0s/uxFmPx4HOj8s/2s/fw40P8D1az+jAkY/SgRgPx4HOj8s/2s/PBJAPyz/az+jAkY/uhFmPzwSQD8s/2s/gA40P8D1az8yIPA+ogtgP9b9vz7mBGY/Gf3XPuYEZj8Z/dc+5gRmPzIg8D7mBGY/jBDMPuYEZj/aAOQ+ogtgP9Hysz7lAkw/jBDMPuYEZj/aAOQ+5gRmP9oA5D7mBGY/1v2/PuUCTD/W/b8+5QJMP9oA5D74/Vk/Gf3XPuUCTD8Z/dc+5QJMP4wQzD7lAkw/2gDkPqELYD/R8rM+5gRmPzIg8D74/Vk/jBDMPuUCTD/aAOQ+5QJMP9b9vz7mBGY/MiDwPqILYD9YEVY/pvRrP40Abj+yAGY/JwNiPwnvQz8nA2I/Cu9DPz8KUD+m9Gs/jQBuPwryXz9YEVY/pvRrP9sNaD+zAGY/cyhcPwnvQz/bDWg/Cu9DP3NoXD+m9Gs/2w1oPwryXz+NAG4/swBmP1gRVj8J70M/J0NiP6b0az8nQ2I/pvRrP40Abj+m9Gs/PQpQPwnvQz/bDWg/tABmP1gRVj8K70M/c2hcP6b0az/bDWg/pvRrP9sNaD+m9Gs/cyhcPwrvQz+tChg/G/1rP3z2KT+KBGY/M/sjP9cSRD+uChg/2BJEP2kFEj8a/Ws/fPYpP1QGYD9pBRI/2BJEPzP7Iz+JBGY/8AIeP9cSRD/wAh4/2BJEP/0EDD/YEkQ/M/sjP1QGYD8z+yM/Gv1rP60KGD/XEkQ/M/sjPxv9az+tChg/G/1rPzP7Iz+KBGY/aQUSP9cSRD989ik/G/1rP2kFEj8b/Ws/8AIePxr9az/wAh4/G/1rP3z2KT+KBGY//AQMPxv9az+jAkY/KwBSP38OND/A9Ws/CvtLP7sRZj8eBzo/LABSPxYBLj8s/2s/CvtLP0oEYD8eBzo/LABSPzwSQD8rAFI/owJGP7sRZj9/DjQ/wPZRPzwSQD8rAFI/owJGP0oEYD+jAkY/LP9rP6MCRj8s/2s/fw40P8D2UT+jAkY/uxFmPx4HOj8s/2s/FQEuPy0AUj8K+0s/LP9rPx4HOj8s/2s/PBJAPyz/az8K+0s/uxFmP4AOND/A9Ws/PBJAPyz/az8yIPA++f1ZP9b9vz7lAkw/G/3XPuYEZj/aAOQ+5QJMPzIg8D6iC2A/0fKzPuUCTD/aAOQ++P1ZP4wQzD7mBGY/jBDMPuYEZj8b/dc+5QJMP9oA5D6iC2A/1v2/PuYEZj/V/b8+5gRmPzIg8D7mBGY/G/3XPuUCTD/aAOQ+5gRmP9Hysz7mBGY/MiDwPqILYD+MEMw+5QJMP9oA5D7mBGY/jBDMPuUCTD8b/dc+5gRmP9b9vz7lAkw/2gDkPqILYD/bDWg/CvJfP1gRVj8J70M/2g1oPwnvQz9yKFw/Cu9DP9sNaD+yAGY/cihcPwnvQz8/ClA/pvRrP40Abj8K8l8/JwNiPwnvQz8nA2I/Cu9DP1gRVj+m9Gs/jQBuP7QAZj+NAG4/tABmP1gRVj+m9Gs/2w1oP6b0az9yaFw/pvRrP40Abj+m9Gs/cWhcP6b0az89ClA/Cu9DP9sNaD+0AGY/J0NiP6b0az8nQ2I/pvRrP1gRVj8K70M/2w1oP6b0az8BAA4AFAABABQABwAKAAYAEwAKABMAFwAVABIADAAVAAwADwAQAAMACQAQAAkAFgAFAAIACAAFAAgACwARAA0AAAARAAAABABMAFIALABMACwAHwAiAB4AKwAiACsALwBIAFAAJABIACQAJwBLAFUAIQBLACEALgBWAE4AIABWACAAIwApACUAGAApABgAHAA3AEIAPAA3ADwAMQA9AD8ANAA9ADQAMgBGADsANQBGADUAQAAwADMAOQAwADkANgBEAEcAQQBEAEEAPgA4ADoARQA4AEUAQwAdABoATwAdAE8AVwAoABsAVAAoAFQASgAtACoAUQAtAFEASQAZACYAUwAZAFMATQBYAFsAYQBYAGEAXgBfAGIAbQBfAG0AagBsAG8AaQBsAGkAZgBlAGgAXQBlAF0AWgBgAGsAZABgAGQAWQBuAGMAXABuAFwAZwBwAHMAeQBwAHkAdgB4AHoAhQB4AIUAgwCEAIcAgQCEAIEAfgB9AH8AdAB9AHQAcgB3AIIAfAB3AHwAcQCGAHsAdQCGAHUAgACIAIsAkQCIAJEAjgCQAJIAnQCQAJ0AmwCcAJ8AmQCcAJkAlgCVAJcAjACVAIwAigCPAJoAlACPAJQAiQCeAJMAjQCeAI0AmACiAKgAqwCiAKsApQCnALQAtwCnALcAqgCyAKwArwCyAK8AtQCtAKAAowCtAKMAsACmAKEArgCmAK4AswC2ALEApAC2AKQAqQC6AMAAwwC6AMMAvQC/AMwAzgC/AM4AwQDKAMQAxwDKAMcAzQDGALkAuwDGALsAyAC+ALgAxQC+AMUAywDPAMkAvADPALwAwgDSANgA2wDSANsA1QDXAOQA5gDXAOYA2QDiANwA3wDiAN8A5QDeANEA0wDeANMA4ADWANAA3QDWAN0A4wDnAOEA1ADnANQA2gDpAOsA8QDpAPEA7wDwAPIA/QDwAP0A+wD8AP4A+AD8APgA9gD1APcA7AD1AOwA6gDuAPoA9ADuAPQA6AD/APMA7QD/AO0A+QAAAQMBCQEAAQkBBgEHAQoBFQEHARUBEgEUARcBEQEUAREBDgENARABBQENAQUBAgEIARMBDAEIAQwBAQEWAQsBBAEWAQQBDwEpAR0BIwEpASMBLwEoASYBGQEoARkBGwEtASsBJQEtASUBJwEhAR8BLAEhASwBLgEYASQBKgEYASoBHgEcARoBIAEcASABIgExATMBOgExAToBOAE3ATkBRQE3AUUBQwFEAUYBPwFEAT8BPQE+AUABNAE+ATQBMgE2AUIBPAE2ATwBMAFHATsBNQFHATUBQQFJAUsBUQFJAVEBTwFQAVIBXQFQAV0BWwFcAV4BWAFcAVgBVgFVAVcBTAFVAUwBSgFOAVoBVAFOAVQBSAFfAVMBTQFfAU0BWQFgAWMBaQFgAWkBZgFoAWoBdQFoAXUBcwF0AXcBcQF0AXEBbgFtAW8BZAFtAWQBYgFnAXIBbAFnAWwBYQF2AWsBZQF2AWUBcAGJAX0BgwGJAYMBjwGIAYYBeQGIAXkBewGNAYsBhQGNAYUBhwGBAX8BjAGBAYwBjgF4AYQBigF4AYoBfgF8AXoBgAF8AYABggGRAZMBmQGRAZkBlwGYAZoBpQGYAaUBowGkAaYBoAGkAaABngGdAZ8BlAGdAZQBkgGWAaIBnAGWAZwBkAGnAZsBlQGnAZUBoQGpAasBsQGpAbEBrwGwAbIBvQGwAb0BuwG8Ab4BuAG8AbgBtgG1AbcBrAG1AawBqgGuAboBtAGuAbQBqAG/AbMBrQG/Aa0BuQHAAcMByQHAAckBxgHIAcoB1QHIAdUB0wHUAdcB0QHUAdEBzgHNAc8BxAHNAcQBwgHHAdIBzAHHAcwBwQHWAcsBxQHWAcUB0AHpAd0B4wHpAeMB7wHoAeYB2QHoAdkB2wHtAesB5QHtAeUB5wHhAd8B7AHhAewB7gHYAeQB6gHYAeoB3gHcAdoB4AHcAeAB4gHxAfMB+QHxAfkB9wH4AfoBBQL4AQUCAwIEAgYCAAIEAgAC/gH9Af8B9AH9AfQB8gH2AQIC/AH2AfwB8AEHAvsB9QEHAvUBAQIJAgsCEQIJAhECDwIQAhICHQIQAh0CGwIcAh4CGAIcAhgCFgIVAhcCDAIVAgwCCgIOAhoCFAIOAhQCCAIfAhMCDQIfAg0CGQIgAiMCKQIgAikCJgIoAisCNgIoAjYCMwI0AjcCMQI0AjECLgIsAi8CJAIsAiQCIQInAjICLQInAi0CIgI1AioCJQI1AiUCMAJJAj0CQwJJAkMCTwJIAkYCOQJIAjkCOwJNAksCRQJNAkUCRwJBAj8CTAJBAkwCTgI4AkQCSgI4AkoCPgI8AjoCQAI8AkACQgJSAlQCWQJSAlkCVwJYAloCZgJYAmYCZAJjAmUCYAJjAmACXgJdAl8CUwJdAlMCUQJWAmICXAJWAlwCUAJnAlsCVQJnAlUCYQJpAm8CcwJpAnMCbQJuAnsCfwJuAn8CcgJ6AnQCeAJ6AngCfgJ1AmgCbAJ1AmwCeQJwAmoCdgJwAnYCfAJ9AncCawJ9AmsCcQKCAogCiwKCAosChQKHApQClwKHApcCigKSAowCjwKSAo8ClQKNAoACgwKNAoMCkAKGAoECjgKGAo4CkwKWApEChAKWAoQCiQKnAq0CoQKnAqECmwKoAp0CmQKoApkCpAKvAqkCpQKvAqUCqwKjAq4CqgKjAqoCnwKaAqACrAKaAqwCpgKcAqICngKcAp4CmAKxArYCugKxAroCtQK3AsMCxwK3AscCuwLCAr0CwQLCAsECxgK8ArACtAK8ArQCwAK4ArICvgK4Ar4CxALFAr8CswLFArMCuQLJAs8C0wLJAtMCzQLOAtsC3wLOAt8C0gLaAtQC2ALaAtgC3gLVAsgCzALVAswC2QLQAsoC1gLQAtYC3ALdAtcCywLdAssC0QLiAugC6wLiAusC5QLmAvMC9wLmAvcC6gLyAuwC7wLyAu8C9QLtAuAC5ALtAuQC8QLnAuEC7gLnAu4C9AL2AvAC4wL2AuMC6QIHAw0DAQMHAwED+wIIA/0C+QIIA/kCBAMPAwkDBQMPAwUDCwMDAw4DCgMDAwoD/wL6AgADDAP6AgwDBgP8AgID/gL8Av4C+AIRAxcDGwMRAxsDFQMWAyMDJwMWAycDGgMiAxwDIAMiAyADJgMdAxADFAMdAxQDIQMYAxIDHgMYAx4DJAMlAx8DEwMlAxMDGQMpAy8DMwMpAzMDLQMuAzsDPwMuAz8DMgM6AzQDOAM6AzgDPgM1AygDLAM1AywDOQMwAyoDNgMwAzYDPAM9AzcDKwM9AysDMQNCA0gDSwNCA0sDRQNGA1MDVwNGA1cDSgNSA0wDTwNSA08DVQNNA0ADRANNA0QDUQNHA0EDTgNHA04DVANWA1ADQwNWA0MDSQNnA20DYQNnA2EDWwNoA10DWQNoA1kDZANvA2kDZQNvA2UDawNjA24DagNjA2oDXwNaA2ADbANaA2wDZgNcA2IDXgNcA14DWANxA3cDewNxA3sDdQN2A4MDhwN2A4cDegOCA3wDgAOCA4ADhgN9A3ADdAN9A3QDgQN4A3IDfgN4A34DhAOFA38DcwOFA3MDeQOJA48DkwOJA5MDjQOOA5sDnwOOA58DkgOaA5QDmAOaA5gDngOVA4gDjAOVA4wDmQOQA4oDlgOQA5YDnAOdA5cDiwOdA4sDkQOiA6gDqwOiA6sDpQOmA7MDtgOmA7YDqQOyA6wDrwOyA68DtQOuA6EDpAOuA6QDsQOnA6ADrQOnA60DtAO3A7ADowO3A6MDqgPHA80DwQPHA8EDuwPIA70DuQPIA7kDxAPPA8kDxQPPA8UDywPDA84DygPDA8oDvwO6A8ADzAO6A8wDxgO8A8IDvgO8A74DuAPQA9cD2wPQA9sD1APWA+ID5gPWA+YD2gPjA9wD4APjA+AD5wPdA9ED1QPdA9UD4QPYA9ID3gPYA94D5APlA98D0wPlA9MD2QM="}]} diff --git a/games/devtest/mods/gltf/models/gltf_triangle_with_vertex_stride.gltf b/games/devtest/mods/gltf/models/gltf_triangle_with_vertex_stride.gltf new file mode 100644 index 000000000..feddfbb02 --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_triangle_with_vertex_stride.gltf @@ -0,0 +1 @@ +{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":1},"indices":0}]}],"buffers":[{"uri":"data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAA=","byteLength":80}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":6,"target":34963},{"buffer":0,"byteOffset":8,"byteLength":72,"byteStride":24,"target":34962}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5123,"count":3,"type":"SCALAR","max":[2],"min":[0]},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":3,"type":"VEC3","max":[1,1,0],"min":[0,0,0]}],"asset":{"version":"2.0"}} diff --git a/games/devtest/mods/gltf/models/gltf_triangle_without_indices.gltf b/games/devtest/mods/gltf/models/gltf_triangle_without_indices.gltf new file mode 100644 index 000000000..e91cc0e5a --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_triangle_without_indices.gltf @@ -0,0 +1 @@ +{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"buffers":[{"uri":"data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA","byteLength":36}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":36,"target":34962}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5126,"count":3,"type":"VEC3","max":[1,1,0],"min":[0,0,0]}],"asset":{"version":"2.0"}} diff --git a/games/devtest/mods/gltf/textures/gltf_cube.png b/games/devtest/mods/gltf/textures/gltf_cube.png new file mode 100644 index 0000000000000000000000000000000000000000..1d019108550471be78c1efa02d4367a5aaacdc92 GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0P3?wHke>@jRu?6^qxc>kD|E9eB^b_}10Yw@- zT^vI+&hMRgm-B!EPs?MDz6TnTOJ_@LQFZ=u)N}1I`3qqhB2w{o=bEjB7#buN_JkT= zklJ;8HkW(z-bQ}jZ8vMquQFZKQ}e`}$+SSlVV20tH7!q0W{TZ3P_wxBxO0)kBPKcB zF#pE(4z9h&cK@~bm2E%&{ZU)l=bvI9miBHu&l=+9n`|96og3&D22WQ%mvv4FO#sL{ BPoDq) literal 0 HcmV?d00001 diff --git a/games/devtest/mods/gltf/textures/gltf_frog.png b/games/devtest/mods/gltf/textures/gltf_frog.png new file mode 100644 index 0000000000000000000000000000000000000000..552ae36493c59c6107de9eb9c08b85cf494d3647 GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvi2$DvS0D`p(auuIo^pl3@)r** zx_@QWpC9+F^;c*BB^XPB{DK)Ap4~_Ta+Z0zIEF|_-aUA+uqi>H^`iAA)g?_HjC?@{ zj{W`jy*_R^t3pE7>UrIx=4=#B{D``d7B}@YEIM2ItZx z*QMOrdNy~*B)&;M7Pqfs_j>8Tuikej{qR=KR}%X?qh{`}?U|hNz{{a{{?cuXs%EB7 z-B&WO-uQeqJJq*WdS$6@&$mZzi4UVfYQC+ry|tl(VN2s7um6qnCOWNuCjLFyc#?Pi RV-=9cJYD@<);T3K0RXN~Zvg-R literal 0 HcmV?d00001 diff --git a/games/devtest/mods/gltf/textures/gltf_snow_man.png b/games/devtest/mods/gltf/textures/gltf_snow_man.png new file mode 100644 index 0000000000000000000000000000000000000000..7f2784358b94af2238337bc6a1555c32c06f9c80 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDB3?!H8JlO)I_yc@GT!AzY1Sgi{RrLJ-|3BuI z$}}LKu_VYZn8D%MjWi%9$J50z#G?1@g@e2e3Oub3*Et+w+0f|Y@aet%Wc9!^s~5)Y zx$%1XzC*^p(<>Q_8F+1fGx*F_*5a_@s<{yO`N)DPK`K|SZOcD!CZ=Hi>5HpcmN2|X z;k9~o)lFniy^Eslft!zXSM}YSuF%?e`lIZ5wHa=yVzLW>_A+?7`njxgN@xNA7I;nX literal 0 HcmV?d00001 diff --git a/games/devtest/mods/gltf/textures/gltf_spider.png b/games/devtest/mods/gltf/textures/gltf_spider.png new file mode 100644 index 0000000000000000000000000000000000000000..1e3d3ae8cbb31c80219081ed7ebdc1bb1ad78373 GIT binary patch literal 10957 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 literal 0 HcmV?d00001 diff --git a/irr/include/IMesh.h b/irr/include/IMesh.h index 6d06eb762..8ee180d5d 100644 --- a/irr/include/IMesh.h +++ b/irr/include/IMesh.h @@ -86,6 +86,17 @@ public: mesh buffer. */ virtual IMeshBuffer *getMeshBuffer(const video::SMaterial &material) const = 0; + //! Minetest binds textures (node tiles, object textures) to models. + // glTF allows multiple primitives (mesh buffers) to reference the same texture. + // This is reflected here: This function gets the texture slot for a mesh buffer. + /** \param meshbufNr: Zero based index of the mesh buffer. The maximum value is + getMeshBufferCount() - 1; + \return number of texture slot to bind to the given mesh buffer */ + virtual u32 getTextureSlot(u32 meshbufNr) const + { + return meshbufNr; + } + //! Get an axis aligned bounding box of the mesh. /** \return Bounding box of this mesh. */ virtual const core::aabbox3d &getBoundingBox() const = 0; diff --git a/irr/include/ISkinnedMesh.h b/irr/include/ISkinnedMesh.h index 9cc7469cb..bb611bba2 100644 --- a/irr/include/ISkinnedMesh.h +++ b/irr/include/ISkinnedMesh.h @@ -199,6 +199,9 @@ public: //! Adds a new meshbuffer to the mesh, access it as last one virtual SSkinMeshBuffer *addMeshBuffer() = 0; + //! Adds a new meshbuffer to the mesh, access it as last one + virtual void addMeshBuffer(SSkinMeshBuffer *meshbuf) = 0; + //! Adds a new joint to the mesh, access it as last one virtual SJoint *addJoint(SJoint *parent = 0) = 0; diff --git a/irr/include/SMesh.h b/irr/include/SMesh.h index a391255a1..66e6ecc08 100644 --- a/irr/include/SMesh.h +++ b/irr/include/SMesh.h @@ -64,6 +64,17 @@ struct SMesh : public IMesh return nullptr; } + u32 getTextureSlot(u32 meshbufNr) const override + { + return TextureSlots.at(meshbufNr); + } + + void setTextureSlot(u32 meshbufNr, u32 textureSlot) + { + TextureSlots.at(meshbufNr) = textureSlot; + } + + //! returns an axis aligned bounding box const core::aabbox3d &getBoundingBox() const override { @@ -103,6 +114,7 @@ struct SMesh : public IMesh if (buf) { buf->grab(); MeshBuffers.push_back(buf); + TextureSlots.push_back(getMeshBufferCount() - 1); } } @@ -122,6 +134,8 @@ struct SMesh : public IMesh //! The meshbuffers of this mesh std::vector MeshBuffers; + //! Mapping from meshbuffer number to bindable texture slot + std::vector TextureSlots; //! The bounding box of this mesh core::aabbox3d BoundingBox; diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index fe9d78321..2b71cf6c5 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -6,6 +6,7 @@ #include "IMeshBuffer.h" #include "S3DVertex.h" +#include "irrArray.h" namespace irr { @@ -21,10 +22,14 @@ struct SSkinMeshBuffer : public IMeshBuffer PrimitiveType(EPT_TRIANGLES), HWBuffer(nullptr), MappingHint_Vertex(EHM_NEVER), MappingHint_Index(EHM_NEVER), BoundingBoxNeedsRecalculated(true) + {} + + //! Constructor for standard vertices + SSkinMeshBuffer(std::vector &&vertices, std::vector &&indices) : + SSkinMeshBuffer() { -#ifdef _DEBUG - setDebugName("SSkinMeshBuffer"); -#endif + Vertices_Standard = std::move(vertices); + Indices = std::move(indices); } //! Get Material of this buffer. diff --git a/irr/include/irrArray.h b/irr/include/irrArray.h index 66978048f..9f390e79b 100644 --- a/irr/include/irrArray.h +++ b/irr/include/irrArray.h @@ -45,6 +45,10 @@ public: { } + //! Move constructor + array(std::vector &&data) : + m_data(std::move(data)), is_sorted(false) {} + //! Reallocates the array, make it bigger or smaller. /** \param new_size New size of array. \param canShrink Specifies whether the array is reallocated even if diff --git a/irr/include/irrString.h b/irr/include/irrString.h index a583c9e4b..9d9b288d8 100644 --- a/irr/include/irrString.h +++ b/irr/include/irrString.h @@ -11,7 +11,6 @@ #include #include #include -#include /* HACK: import these string methods from MT's util/string.h */ extern std::wstring utf8_to_wide(std::string_view input); @@ -65,6 +64,7 @@ static inline u32 locale_upper(u32 x) template class string { + using stl_type = std::basic_string; public: typedef T char_type; @@ -79,6 +79,10 @@ public: *this = other; } + string(const stl_type &str) : str(str) {} + + string(stl_type &&str) : str(std::move(str)) {} + //! Constructor from other string types template string(const string &other) @@ -814,13 +818,6 @@ public: friend size_t wStringToUTF8(stringc &destination, const wchar_t *source); private: - typedef std::basic_string stl_type; - - //! Private constructor - string(stl_type &&str) : - str(str) - { - } //! strlen wrapper template diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp new file mode 100644 index 000000000..64bbc10f1 --- /dev/null +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -0,0 +1,695 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "CGLTFMeshFileLoader.h" + +#include "coreutil.h" +#include "CSkinnedMesh.h" +#include "ISkinnedMesh.h" +#include "irrTypes.h" +#include "IReadFile.h" +#include "matrix4.h" +#include "path.h" +#include "quaternion.h" +#include "vector3d.h" +#include "os.h" + +#include "tiniergltf.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace irr { + +/* Notes on the coordinate system. + * + * glTF uses a right-handed coordinate system where +Z is the + * front-facing axis, and Irrlicht uses a left-handed coordinate + * system where -Z is the front-facing axis. + * We convert between them by mirroring the mesh across the X axis. + * Doing this correctly requires negating the Z coordinate on + * vertex positions and normals, and reversing the winding order + * of the vertex indices. + */ + +// Right-to-left handedness conversions + +template +static inline T convertHandedness(const T &t); + +template <> +core::vector3df convertHandedness(const core::vector3df &p) +{ + return core::vector3df(p.X, p.Y, -p.Z); +} + +namespace scene { + +using SelfType = CGLTFMeshFileLoader; + +template +SelfType::Accessor +SelfType::Accessor::sparseIndices(const tiniergltf::GlTF &model, + const tiniergltf::AccessorSparseIndices &indices, + const std::size_t count) +{ + const auto &view = model.bufferViews->at(indices.bufferView); + const auto byteStride = view.byteStride.value_or(indices.elementSize()); + + const auto &buffer = model.buffers->at(view.buffer); + const auto source = buffer.data.data() + view.byteOffset + indices.byteOffset; + + return SelfType::Accessor(source, byteStride, count); +} + +template +SelfType::Accessor +SelfType::Accessor::sparseValues(const tiniergltf::GlTF &model, + const tiniergltf::AccessorSparseValues &values, + const std::size_t count, + const std::size_t defaultByteStride) +{ + const auto &view = model.bufferViews->at(values.bufferView); + const auto byteStride = view.byteStride.value_or(defaultByteStride); + + const auto &buffer = model.buffers->at(view.buffer); + const auto source = buffer.data.data() + view.byteOffset + values.byteOffset; + + return SelfType::Accessor(source, byteStride, count); +} + +template +SelfType::Accessor +SelfType::Accessor::base(const tiniergltf::GlTF &model, std::size_t accessorIdx) +{ + const auto &accessor = model.accessors->at(accessorIdx); + + if (!accessor.bufferView.has_value()) { + return Accessor(accessor.count); + } + + const auto &view = model.bufferViews->at(accessor.bufferView.value()); + const auto byteStride = view.byteStride.value_or(accessor.elementSize()); + + const auto &buffer = model.buffers->at(view.buffer); + const auto source = buffer.data.data() + view.byteOffset + accessor.byteOffset; + + return Accessor(source, byteStride, accessor.count); +} + +template +SelfType::Accessor +SelfType::Accessor::make(const tiniergltf::GlTF &model, std::size_t accessorIdx) +{ + const auto &accessor = model.accessors->at(accessorIdx); + if (accessor.componentType != getComponentType() || accessor.type != getType()) + throw std::runtime_error("invalid accessor"); + + const auto base = Accessor::base(model, accessorIdx); + + if (accessor.sparse.has_value()) { + std::vector vec(accessor.count); + for (std::size_t i = 0; i < accessor.count; ++i) { + vec[i] = base.get(i); + } + const auto overriddenCount = accessor.sparse->count; + const auto indicesAccessor = ([&]() -> AccessorVariant { + switch (accessor.sparse->indices.componentType) { + case tiniergltf::AccessorSparseIndices::ComponentType::UNSIGNED_BYTE: + return Accessor::sparseIndices(model, accessor.sparse->indices, overriddenCount); + case tiniergltf::AccessorSparseIndices::ComponentType::UNSIGNED_SHORT: + return Accessor::sparseIndices(model, accessor.sparse->indices, overriddenCount); + case tiniergltf::AccessorSparseIndices::ComponentType::UNSIGNED_INT: + return Accessor::sparseIndices(model, accessor.sparse->indices, overriddenCount); + } + throw std::logic_error("invalid enum value"); + })(); + + const auto valuesAccessor = Accessor::sparseValues(model, + accessor.sparse->values, overriddenCount, + accessor.bufferView.has_value() + ? model.bufferViews->at(*accessor.bufferView).byteStride.value_or(accessor.elementSize()) + : accessor.elementSize()); + + for (std::size_t i = 0; i < overriddenCount; ++i) { + u32 index; + std::visit([&](auto &&acc) { index = acc.get(i); }, indicesAccessor); + if (index >= accessor.count) + throw std::runtime_error("index out of bounds"); + vec[index] = valuesAccessor.get(i); + } + return Accessor(vec, accessor.count); + } + + return base; +} + +#define ACCESSOR_TYPES(T, U, V) \ + template <> \ + constexpr tiniergltf::Accessor::Type SelfType::Accessor::getType() \ + { \ + return tiniergltf::Accessor::Type::U; \ + } \ + template <> \ + constexpr tiniergltf::Accessor::ComponentType SelfType::Accessor::getComponentType() \ + { \ + return tiniergltf::Accessor::ComponentType::V; \ + } + +#define VEC_ACCESSOR_TYPES(T, U, N) \ + template <> \ + constexpr tiniergltf::Accessor::Type SelfType::Accessor>::getType() \ + { \ + return tiniergltf::Accessor::Type::VEC##N; \ + } \ + template <> \ + constexpr tiniergltf::Accessor::ComponentType SelfType::Accessor>::getComponentType() \ + { \ + return tiniergltf::Accessor::ComponentType::U; \ + } \ + template <> \ + std::array SelfType::rawget(const char *ptr) \ + { \ + std::array res; \ + for (int i = 0; i < N; ++i) \ + res[i] = rawget(ptr + sizeof(T) * i); \ + return res; \ + } + +#define ACCESSOR_PRIMITIVE(T, U) \ + ACCESSOR_TYPES(T, SCALAR, U) \ + VEC_ACCESSOR_TYPES(T, U, 2) \ + VEC_ACCESSOR_TYPES(T, U, 3) \ + VEC_ACCESSOR_TYPES(T, U, 4) + +ACCESSOR_PRIMITIVE(f32, FLOAT) +ACCESSOR_PRIMITIVE(u8, UNSIGNED_BYTE) +ACCESSOR_PRIMITIVE(u16, UNSIGNED_SHORT) +ACCESSOR_PRIMITIVE(u32, UNSIGNED_INT) + +ACCESSOR_TYPES(core::vector3df, VEC3, FLOAT) + +template +T SelfType::Accessor::get(std::size_t i) const +{ + // Buffer-based accessor: Read directly from the buffer. + if (std::holds_alternative(source)) { + const auto bufsrc = std::get(source); + return rawget(bufsrc.ptr + i * bufsrc.byteStride); + } + // Array-based accessor (used for sparse accessors): Read from array. + if (std::holds_alternative>(source)) { + return std::get>(source)[i]; + } + // Default-initialized accessor. + // We differ slightly from glTF here in that + // we default-initialize quaternions and matrices properly, + // but this does not cause any discrepancies for valid glTF models. + std::get>(source); + return T(); +} + +template +T SelfType::rawget(const char *ptr) +{ + T dest; + std::memcpy(&dest, ptr, sizeof(dest)); +#ifdef __BIG_ENDIAN__ + return os::Byteswap::byteswap(dest); +#else + return dest; +#endif +} + +// Note that these "more specialized templates" should win. + +template <> +core::matrix4 SelfType::rawget(const char *ptr) +{ + core::matrix4 mat; + for (u8 i = 0; i < 16; ++i) { + mat[i] = rawget(ptr + i * sizeof(f32)); + } + return mat; +} + +template <> +core::vector3df SelfType::rawget(const char *ptr) +{ + return core::vector3df( + rawget(ptr), + rawget(ptr + sizeof(f32)), + rawget(ptr + 2 * sizeof(f32))); +} + +template <> +core::quaternion SelfType::rawget(const char *ptr) +{ + return core::quaternion( + rawget(ptr), + rawget(ptr + sizeof(f32)), + rawget(ptr + 2 * sizeof(f32)), + rawget(ptr + 3 * sizeof(f32))); +} + +template +SelfType::NormalizedValuesAccessor +SelfType::createNormalizedValuesAccessor( + const tiniergltf::GlTF &model, + const std::size_t accessorIdx) +{ + const auto &acc = model.accessors->at(accessorIdx); + switch (acc.componentType) { + case tiniergltf::Accessor::ComponentType::UNSIGNED_BYTE: + return Accessor>::make(model, accessorIdx); + case tiniergltf::Accessor::ComponentType::UNSIGNED_SHORT: + return Accessor>::make(model, accessorIdx); + case tiniergltf::Accessor::ComponentType::FLOAT: + return Accessor>::make(model, accessorIdx); + default: + throw std::runtime_error("invalid component type"); + } +} + +template +std::array SelfType::getNormalizedValues( + const NormalizedValuesAccessor &accessor, + const std::size_t i) +{ + std::array values; + if (std::holds_alternative>>(accessor)) { + const auto u8s = std::get>>(accessor).get(i); + for (u8 i = 0; i < N; ++i) + values[i] = static_cast(u8s[i]) / std::numeric_limits::max(); + } else if (std::holds_alternative>>(accessor)) { + const auto u16s = std::get>>(accessor).get(i); + for (u8 i = 0; i < N; ++i) + values[i] = static_cast(u16s[i]) / std::numeric_limits::max(); + } else { + values = std::get>>(accessor).get(i); + for (u8 i = 0; i < N; ++i) { + if (values[i] < 0 || values[i] > 1) + throw std::runtime_error("invalid normalized value"); + } + } + return values; +} + +/** + * The most basic portion of the code base. This tells irllicht if this file has a .gltf extension. +*/ +bool SelfType::isALoadableFileExtension( + const io::path& filename) const +{ + return core::hasFileExtension(filename, "gltf"); +} + +/** + * Entry point into loading a GLTF model. +*/ +IAnimatedMesh* SelfType::createMesh(io::IReadFile* file) +{ + if (file->getSize() <= 0) { + return nullptr; + } + std::optional model = tryParseGLTF(file); + if (!model.has_value()) { + return nullptr; + } + + if (!(model->buffers.has_value() + && model->bufferViews.has_value() + && model->accessors.has_value() + && model->meshes.has_value() + && model->nodes.has_value())) { + os::Printer::log("glTF loader", "missing required fields", ELL_ERROR); + return nullptr; + } + + auto *mesh = new CSkinnedMesh(); + MeshExtractor parser(std::move(model.value()), mesh); + try { + parser.loadNodes(); + } catch (std::runtime_error &e) { + os::Printer::log("glTF loader", e.what(), ELL_ERROR); + mesh->drop(); + return nullptr; + } + if (model->images.has_value()) + os::Printer::log("glTF loader", "embedded images are not supported", ELL_WARNING); + return mesh; +} + +static void transformVertices(std::vector &vertices, const core::matrix4 &transform) +{ + for (auto &vertex : vertices) { + // Apply scaling, rotation and rotation (in that order) to the position. + transform.transformVect(vertex.Pos); + // For the normal, we do not want to apply the translation. + // TODO note that this also applies scaling; the Irrlicht method is misnamed. + transform.rotateVect(vertex.Normal); + // Renormalize (length might have been affected by scaling). + vertex.Normal.normalize(); + } +} + +static void checkIndices(const std::vector &indices, const std::size_t nVerts) +{ + for (u16 index : indices) { + if (index >= nVerts) + throw std::runtime_error("index out of bounds"); + } +} + +static std::vector generateIndices(const std::size_t nVerts) +{ + std::vector indices(nVerts); + for (std::size_t i = 0; i < nVerts; i += 3) { + // Reverse winding order per triangle + indices[i] = i + 2; + indices[i + 1] = i + 1; + indices[i + 2] = i; + } + return indices; +} + +/** + * Load up the rawest form of the model. The vertex positions and indices. + * Documentation: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes + * If material is undefined, then a default material MUST be used. +*/ +void SelfType::MeshExtractor::loadMesh( + const std::size_t meshIdx, + ISkinnedMesh::SJoint *parent) const +{ + for (std::size_t pi = 0; pi < getPrimitiveCount(meshIdx); ++pi) { + const auto &primitive = m_gltf_model.meshes->at(meshIdx).primitives.at(pi); + auto vertices = getVertices(primitive); + if (!vertices.has_value()) + continue; // "When positions are not specified, client implementations SHOULD skip primitive’s rendering" + + // Excludes the max value for consistency. + if (vertices->size() >= std::numeric_limits::max()) + throw std::runtime_error("too many vertices"); + + // Apply the global transform along the parent chain. + transformVertices(*vertices, parent->GlobalMatrix); + + auto maybeIndices = getIndices(primitive); + std::vector indices; + if (maybeIndices.has_value()) { + indices = std::move(*maybeIndices); + checkIndices(indices, vertices->size()); + } else { + // Non-indexed geometry + indices = generateIndices(vertices->size()); + } + + m_irr_model->addMeshBuffer( + new SSkinMeshBuffer(std::move(*vertices), std::move(indices))); + + if (primitive.material.has_value()) { + const auto &material = m_gltf_model.materials->at(*primitive.material); + if (material.pbrMetallicRoughness.has_value()) { + const auto &texture = material.pbrMetallicRoughness->baseColorTexture; + if (texture.has_value()) { + const auto meshbufNr = m_irr_model->getMeshBufferCount() - 1; + m_irr_model->setTextureSlot(meshbufNr, static_cast(texture->index)); + } + } + } + } +} + +// Base transformation between left & right handed coordinate systems. +// This just inverts the Z axis. +static const core::matrix4 leftToRight = core::matrix4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1 +); +static const core::matrix4 rightToLeft = leftToRight; + +static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m) +{ + // Note: Under the hood, this casts these doubles to floats. + return core::matrix4( + m[0], m[1], m[2], m[3], + m[4], m[5], m[6], m[7], + m[8], m[9], m[10], m[11], + m[12], m[13], m[14], m[15]); +} + +static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs) +{ + const auto &trans = trs.translation; + const auto &rot = trs.rotation; + const auto &scale = trs.scale; + core::matrix4 transMat; + transMat.setTranslation(core::vector3df(trans[0], trans[1], trans[2])); + core::matrix4 rotMat = core::quaternion(rot[0], rot[1], rot[2], rot[3]).getMatrix(); + core::matrix4 scaleMat; + scaleMat.setScale(core::vector3df(scale[0], scale[1], scale[2])); + return transMat * rotMat * scaleMat; +} + +static core::matrix4 loadTransform(std::optional> transform) { + if (!transform.has_value()) { + return core::matrix4(); + } + core::matrix4 mat = std::visit([](const auto &t) { return loadTransform(t); }, *transform); + return rightToLeft * mat * leftToRight; +} + +void SelfType::MeshExtractor::loadNode( + const std::size_t nodeIdx, + ISkinnedMesh::SJoint *parent) const +{ + const auto &node = m_gltf_model.nodes->at(nodeIdx); + auto *joint = m_irr_model->addJoint(parent); + const core::matrix4 transform = loadTransform(node.transform); + joint->LocalMatrix = transform; + joint->GlobalMatrix = parent ? parent->GlobalMatrix * joint->LocalMatrix : joint->LocalMatrix; + if (node.name.has_value()) { + joint->Name = node.name->c_str(); + } + if (node.mesh.has_value()) { + loadMesh(*node.mesh, joint); + } + if (node.children.has_value()) { + for (const auto &child : *node.children) { + loadNode(child, joint); + } + } +} + +void SelfType::MeshExtractor::loadNodes() const +{ + std::vector isChild(m_gltf_model.nodes->size()); + for (const auto &node : *m_gltf_model.nodes) { + if (!node.children.has_value()) + continue; + for (const auto &child : *node.children) { + isChild[child] = true; + } + } + // Load all nodes that aren't children. + // Children will be loaded by their parent nodes. + for (std::size_t i = 0; i < m_gltf_model.nodes->size(); ++i) { + if (!isChild[i]) { + loadNode(i, nullptr); + } + } +} + +/** + * Extracts GLTF mesh indices. + */ +std::optional> SelfType::MeshExtractor::getIndices( + const tiniergltf::MeshPrimitive &primitive) const +{ + const auto accessorIdx = primitive.indices; + if (!accessorIdx.has_value()) + return std::nullopt; // non-indexed geometry + + const auto accessor = ([&]() -> AccessorVariant { + const auto &acc = m_gltf_model.accessors->at(*accessorIdx); + switch (acc.componentType) { + case tiniergltf::Accessor::ComponentType::UNSIGNED_BYTE: + return Accessor::make(m_gltf_model, *accessorIdx); + case tiniergltf::Accessor::ComponentType::UNSIGNED_SHORT: + return Accessor::make(m_gltf_model, *accessorIdx); + case tiniergltf::Accessor::ComponentType::UNSIGNED_INT: + return Accessor::make(m_gltf_model, *accessorIdx); + default: + throw std::runtime_error("invalid component type"); + } + })(); + const auto count = std::visit([](auto &&a) { return a.getCount(); }, accessor); + + std::vector indices; + for (std::size_t i = 0; i < count; ++i) { + // TODO (low-priority, maybe never) also reverse winding order based on determinant of global transform + // FIXME this hack also reverses triangle draw order + std::size_t elemIdx = count - i - 1; // reverse index order + u16 index; + // Note: glTF forbids the max value for each component type. + if (std::holds_alternative>(accessor)) { + index = std::get>(accessor).get(elemIdx); + if (index == std::numeric_limits::max()) + throw std::runtime_error("invalid index"); + } else if (std::holds_alternative>(accessor)) { + index = std::get>(accessor).get(elemIdx); + if (index == std::numeric_limits::max()) + throw std::runtime_error("invalid index"); + } else if (std::holds_alternative>(accessor)) { + u32 indexWide = std::get>(accessor).get(elemIdx); + // Use >= here for consistency. + if (indexWide >= std::numeric_limits::max()) + throw std::runtime_error("index too large (>= 65536)"); + index = static_cast(indexWide); + } + indices.push_back(index); + } + + return indices; +} + +/** + * Create a vector of video::S3DVertex (model data) from a mesh & primitive index. + */ +std::optional> SelfType::MeshExtractor::getVertices( + const tiniergltf::MeshPrimitive &primitive) const +{ + const auto &attributes = primitive.attributes; + const auto positionAccessorIdx = attributes.position; + if (!positionAccessorIdx.has_value()) { + // "When positions are not specified, client implementations SHOULD skip primitive's rendering" + return std::nullopt; + } + + std::vector vertices; + const auto vertexCount = m_gltf_model.accessors->at(*positionAccessorIdx).count; + vertices.resize(vertexCount); + copyPositions(*positionAccessorIdx, vertices); + + const auto normalAccessorIdx = attributes.normal; + if (normalAccessorIdx.has_value()) { + copyNormals(normalAccessorIdx.value(), vertices); + } + // TODO verify that the automatic normal recalculation done in Minetest indeed works correctly + + const auto &texcoords = attributes.texcoord; + if (texcoords.has_value()) { + const auto tCoordAccessorIdx = texcoords->at(0); + copyTCoords(tCoordAccessorIdx, vertices); + } + + return vertices; +} + +/** + * Get the amount of meshes that a model contains. +*/ +std::size_t SelfType::MeshExtractor::getMeshCount() const +{ + return m_gltf_model.meshes->size(); +} + +/** + * Get the amount of primitives that a mesh in a model contains. +*/ +std::size_t SelfType::MeshExtractor::getPrimitiveCount( + const std::size_t meshIdx) const +{ + return m_gltf_model.meshes->at(meshIdx).primitives.size(); +} + +/** + * Streams vertex positions raw data into usable buffer via reference. + * Buffer: ref Vector +*/ +void SelfType::MeshExtractor::copyPositions( + const std::size_t accessorIdx, + std::vector& vertices) const +{ + const auto accessor = Accessor::make(m_gltf_model, accessorIdx); + for (std::size_t i = 0; i < accessor.getCount(); i++) { + vertices[i].Pos = convertHandedness(accessor.get(i)); + } +} + +/** + * Streams normals raw data into usable buffer via reference. + * Buffer: ref Vector +*/ +void SelfType::MeshExtractor::copyNormals( + const std::size_t accessorIdx, + std::vector& vertices) const +{ + const auto accessor = Accessor::make(m_gltf_model, accessorIdx); + for (std::size_t i = 0; i < accessor.getCount(); ++i) { + vertices[i].Normal = convertHandedness(accessor.get(i)); + } +} + +/** + * Streams texture coordinate raw data into usable buffer via reference. + * Buffer: ref Vector +*/ +void SelfType::MeshExtractor::copyTCoords( + const std::size_t accessorIdx, + std::vector& vertices) const +{ + const auto accessor = createNormalizedValuesAccessor<2>(m_gltf_model, accessorIdx); + const auto count = std::visit([](auto &&a) { return a.getCount(); }, accessor); + for (std::size_t i = 0; i < count; ++i) { + const auto vals = getNormalizedValues(accessor, i); + vertices[i].TCoords = core::vector2df(vals[0], vals[1]); + } +} + +/** + * This is where the actual model's GLTF file is loaded and parsed by tiniergltf. +*/ +std::optional SelfType::tryParseGLTF(io::IReadFile* file) +{ + auto size = file->getSize(); + if (size < 0) // this can happen if `ftell` fails + return std::nullopt; + std::unique_ptr buf(new char[size + 1]); + if (file->read(buf.get(), size) != static_cast(size)) + return std::nullopt; + // We probably don't need this, but add it just to be sure. + buf[size] = '\0'; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + Json::Value json; + JSONCPP_STRING err; + if (!reader->parse(buf.get(), buf.get() + size, &json, &err)) { + return std::nullopt; + } + try { + return tiniergltf::GlTF(json); + } catch (const std::runtime_error &e) { + os::Printer::log("glTF loader", e.what(), ELL_ERROR); + return std::nullopt; + } catch (const std::out_of_range &e) { + os::Printer::log("glTF loader", e.what(), ELL_ERROR); + return std::nullopt; + } +} + +} // namespace scene + +} // namespace irr + diff --git a/irr/src/CGLTFMeshFileLoader.h b/irr/src/CGLTFMeshFileLoader.h new file mode 100644 index 000000000..39c3ea6dd --- /dev/null +++ b/irr/src/CGLTFMeshFileLoader.h @@ -0,0 +1,147 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "CSkinnedMesh.h" +#include "IMeshLoader.h" +#include "IReadFile.h" +#include "irrTypes.h" +#include "path.h" +#include "S3DVertex.h" + +#include + +#include +#include + +namespace irr +{ + +namespace scene +{ + +class CGLTFMeshFileLoader : public IMeshLoader +{ +public: + CGLTFMeshFileLoader() noexcept {}; + + bool isALoadableFileExtension(const io::path& filename) const override; + + IAnimatedMesh* createMesh(io::IReadFile* file) override; + +private: + template + static T rawget(const char *ptr); + + template + class Accessor + { + struct BufferSource + { + const char *ptr; + std::size_t byteStride; + }; + using Source = std::variant, std::tuple<>>; + + public: + static Accessor sparseIndices( + const tiniergltf::GlTF &model, + const tiniergltf::AccessorSparseIndices &indices, + const std::size_t count); + static Accessor sparseValues( + const tiniergltf::GlTF &model, + const tiniergltf::AccessorSparseValues &values, + const std::size_t count, + const std::size_t defaultByteStride); + static Accessor base( + const tiniergltf::GlTF &model, + std::size_t accessorIdx); + static Accessor make(const tiniergltf::GlTF &model, std::size_t accessorIdx); + static constexpr tiniergltf::Accessor::Type getType(); + static constexpr tiniergltf::Accessor::ComponentType getComponentType(); + std::size_t getCount() const { return count; } + T get(std::size_t i) const; + + private: + Accessor(const char *ptr, std::size_t byteStride, std::size_t count) : + source(BufferSource{ptr, byteStride}), count(count) {} + Accessor(std::vector vec, std::size_t count) : + source(vec), count(count) {} + Accessor(std::size_t count) : + source(std::make_tuple()), count(count) {} + // Directly from buffer, sparse, or default-initialized + const Source source; + const std::size_t count; + }; + + template + using AccessorVariant = std::variant...>; + + template + using ArrayAccessorVariant = std::variant>...>; + + template + using NormalizedValuesAccessor = ArrayAccessorVariant; + + template + static NormalizedValuesAccessor createNormalizedValuesAccessor( + const tiniergltf::GlTF &model, + const std::size_t accessorIdx); + + template + static std::array getNormalizedValues( + const NormalizedValuesAccessor &accessor, + const std::size_t i); + + class MeshExtractor { + public: + MeshExtractor(tiniergltf::GlTF &&model, + CSkinnedMesh *mesh) noexcept + : m_gltf_model(model), m_irr_model(mesh) {}; + + /* Gets indices for the given mesh/primitive. + * + * Values are return in Irrlicht winding order. + */ + std::optional> getIndices( + const tiniergltf::MeshPrimitive &primitive) const; + + std::optional> getVertices( + const tiniergltf::MeshPrimitive &primitive) const; + + std::size_t getMeshCount() const; + + std::size_t getPrimitiveCount(const std::size_t meshIdx) const; + + void loadNodes() const; + + private: + const tiniergltf::GlTF m_gltf_model; + CSkinnedMesh *m_irr_model; + + void copyPositions(const std::size_t accessorIdx, + std::vector& vertices) const; + + void copyNormals(const std::size_t accessorIdx, + std::vector& vertices) const; + + void copyTCoords(const std::size_t accessorIdx, + std::vector& vertices) const; + + void loadMesh( + std::size_t meshIdx, + ISkinnedMesh::SJoint *parentJoint) const; + + void loadNode( + const std::size_t nodeIdx, + ISkinnedMesh::SJoint *parentJoint) const; + }; + + std::optional tryParseGLTF(io::IReadFile* file); +}; + +} // namespace scene + +} // namespace irr + diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 161a0c060..f5bc675e4 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -17,7 +17,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$") set(CMAKE_CXX_FLAGS_RELEASE "-O3") set(CMAKE_CXX_FLAGS_DEBUG "-g") - add_compile_options(-Wall -pipe -fno-exceptions) + add_compile_options(-Wall -pipe) # Enable SSE for floating point math on 32-bit x86 by default # reasoning see minetest issue #11810 and https://gcc.gnu.org/wiki/FloatingPointMath @@ -298,6 +298,7 @@ set(link_includes set(IRRMESHLOADER CB3DMeshFileLoader.cpp + CGLTFMeshFileLoader.cpp COBJMeshFileLoader.cpp CXMeshFileLoader.cpp ) @@ -310,6 +311,8 @@ add_library(IRRMESHOBJ OBJECT ${IRRMESHLOADER} ) +target_link_libraries(IRRMESHOBJ PUBLIC tiniergltf::tiniergltf) + add_library(IRROBJ OBJECT CBillboardSceneNode.cpp CCameraSceneNode.cpp @@ -321,6 +324,10 @@ add_library(IRROBJ OBJECT CMeshCache.cpp ) +# Make sure IRROBJ gets the transitive include directories for +# tiniergltf from IRRMESHOBJ. +target_link_libraries(IRROBJ PRIVATE IRRMESHOBJ) + set(IRRDRVROBJ CNullDriver.cpp CGLXManager.cpp @@ -477,6 +484,7 @@ target_include_directories(IrrlichtMt # this needs to be here and not in a variable (like link_includes) due to issues # with the generator expressions on at least CMake 3.22, but not 3.28 or later target_link_libraries(IrrlichtMt PRIVATE + tiniergltf::tiniergltf ${ZLIB_LIBRARY} ${JPEG_LIBRARY} ${PNG_LIBRARY} diff --git a/irr/src/CSceneManager.cpp b/irr/src/CSceneManager.cpp index cd88e1a6e..c75a28a48 100644 --- a/irr/src/CSceneManager.cpp +++ b/irr/src/CSceneManager.cpp @@ -20,6 +20,7 @@ #include "CXMeshFileLoader.h" #include "COBJMeshFileLoader.h" #include "CB3DMeshFileLoader.h" +#include "CGLTFMeshFileLoader.h" #include "CBillboardSceneNode.h" #include "CAnimatedMeshSceneNode.h" #include "CCameraSceneNode.h" @@ -78,6 +79,7 @@ CSceneManager::CSceneManager(video::IVideoDriver *driver, MeshLoaderList.push_back(new CXMeshFileLoader(this)); MeshLoaderList.push_back(new COBJMeshFileLoader(this)); MeshLoaderList.push_back(new CB3DMeshFileLoader(this)); + MeshLoaderList.push_back(new CGLTFMeshFileLoader()); } //! destructor diff --git a/irr/src/CSkinnedMesh.cpp b/irr/src/CSkinnedMesh.cpp index eb7317309..5db027abc 100644 --- a/irr/src/CSkinnedMesh.cpp +++ b/irr/src/CSkinnedMesh.cpp @@ -6,6 +6,7 @@ #include #include "CBoneSceneNode.h" #include "IAnimatedMeshSceneNode.h" +#include "SSkinMeshBuffer.h" #include "os.h" namespace @@ -596,6 +597,15 @@ IMeshBuffer *CSkinnedMesh::getMeshBuffer(const video::SMaterial &material) const return 0; } +u32 CSkinnedMesh::getTextureSlot(u32 meshbufNr) const +{ + return TextureSlots.at(meshbufNr); +} + +void CSkinnedMesh::setTextureSlot(u32 meshbufNr, u32 textureSlot) { + TextureSlots.at(meshbufNr) = textureSlot; +} + //! returns an axis aligned bounding box const core::aabbox3d &CSkinnedMesh::getBoundingBox() const { @@ -1057,10 +1067,17 @@ void CSkinnedMesh::updateBoundingBox(void) scene::SSkinMeshBuffer *CSkinnedMesh::addMeshBuffer() { scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer(); + TextureSlots.push_back(LocalBuffers.size()); LocalBuffers.push_back(buffer); return buffer; } +void CSkinnedMesh::addMeshBuffer(SSkinMeshBuffer *meshbuf) +{ + TextureSlots.push_back(LocalBuffers.size()); + LocalBuffers.push_back(meshbuf); +} + CSkinnedMesh::SJoint *CSkinnedMesh::addJoint(SJoint *parent) { SJoint *joint = new SJoint; diff --git a/irr/src/CSkinnedMesh.h b/irr/src/CSkinnedMesh.h index b0228c93b..4b4c5e3b7 100644 --- a/irr/src/CSkinnedMesh.h +++ b/irr/src/CSkinnedMesh.h @@ -61,6 +61,10 @@ public: NULL if there is no such mesh buffer. */ IMeshBuffer *getMeshBuffer(const video::SMaterial &material) const override; + u32 getTextureSlot(u32 meshbufNr) const override; + + void setTextureSlot(u32 meshbufNr, u32 textureSlot); + //! returns an axis aligned bounding box const core::aabbox3d &getBoundingBox() const override; @@ -129,6 +133,9 @@ public: //! Adds a new meshbuffer to the mesh, access it as last one SSkinMeshBuffer *addMeshBuffer() override; + //! Adds a new meshbuffer to the mesh, access it as last one + void addMeshBuffer(SSkinMeshBuffer *meshbuf) override; + //! Adds a new joint to the mesh, access it as last one SJoint *addJoint(SJoint *parent = 0) override; @@ -184,6 +191,8 @@ private: core::array *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers core::array LocalBuffers; + //! Mapping from meshbuffer number to bindable texture slot + std::vector TextureSlots; core::array AllJoints; core::array RootJoints; diff --git a/lib/tiniergltf/.gitignore b/lib/tiniergltf/.gitignore new file mode 100644 index 000000000..6d105f52b --- /dev/null +++ b/lib/tiniergltf/.gitignore @@ -0,0 +1,6 @@ +cmake +CMakeCache.txt +CMakeFiles +.cache +compile_commands.json +build \ No newline at end of file diff --git a/lib/tiniergltf/CMakeLists.txt b/lib/tiniergltf/CMakeLists.txt new file mode 100644 index 000000000..889203e49 --- /dev/null +++ b/lib/tiniergltf/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.12) + +project(tiniergltf + VERSION 1.0.0 + LANGUAGES CXX +) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(tiniergltf OBJECT tiniergltf.hpp) +add_library(tiniergltf::tiniergltf ALIAS tiniergltf) +set_target_properties(tiniergltf PROPERTIES LINKER_LANGUAGE CXX) + +target_include_directories(tiniergltf + INTERFACE + "$" + "${JSON_INCLUDE_DIR}" # Set in FindJson.cmake + "${CMAKE_SOURCE_DIR}/src" # util/base64.h +) + +target_link_libraries(tiniergltf) diff --git a/lib/tiniergltf/Readme.md b/lib/tiniergltf/Readme.md new file mode 100644 index 000000000..a8cc65c93 --- /dev/null +++ b/lib/tiniergltf/Readme.md @@ -0,0 +1,39 @@ +# TinierGLTF + +A safe, modern, tiny glTF loader for C++ 17. + +What this is: + +* A tiny glTF deserializer which maps JSON objects to appropriate C++ structures. +* Intended to be safe for loading untrusted input. +* Slightly tailored to the needs of [Minetest](https://github.com/minetest/minetest). + +What this doesn't and shouldn't do: + +* Serialization +* Loading images +* Resolving resources +* Support glTF extensions + +## TODOs + +- [ ] Add GLB support. +- [ ] Add further checks according to the specification. + - Everything in the JSON schema (+ indices and misc. stuff) is already validated. + Much of the code was generated by a Lua script from the JSON schemata. + +## License + +`tiniergltf.hpp` was written by Lars Müller and is licensed under the MIT license: + +> Copyright 2024 Lars Müller +> +> 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. + +## Bug Bounty + +I offer a reward of one (1) virtual headpat per valid bug report. diff --git a/lib/tiniergltf/tiniergltf.hpp b/lib/tiniergltf/tiniergltf.hpp new file mode 100644 index 000000000..6a861556e --- /dev/null +++ b/lib/tiniergltf/tiniergltf.hpp @@ -0,0 +1,1357 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/base64.h" + +namespace tiniergltf { + +static inline void check(bool cond) { + if (!cond) + throw std::runtime_error("invalid glTF"); +} + +template +static inline void checkIndex(const std::optional> &vec, + const std::optional &i) { + if (!i.has_value()) return; + check(vec.has_value()); + check(i < vec->size()); +} + +template +static inline void checkIndex(const std::vector &vec, + const std::optional &i) { + if (!i.has_value()) return; + check(i < vec.size()); +} + +template +static inline void checkForall(const std::optional> &vec, const F &cond) { + if (!vec.has_value()) + return; + for (const T &v : vec.value()) + cond(v); +} + +template +static inline void checkDuplicateFree(const std::vector &vec) { + check(std::unordered_set(vec.begin(), vec.end()).size() == vec.size()); +} + +template +static inline T as(const Json::Value &o); + +template<> +bool as(const Json::Value &o) { + check(o.isBool()); + return o.asBool(); +} + +template<> +double as (const Json::Value &o) { + check(o.isDouble()); + return o.asDouble(); +} + +template<> +std::size_t as(const Json::Value &o) { + check(o.isUInt64()); + auto u = o.asUInt64(); + check(u <= std::numeric_limits::max()); + return u; +} + +template<> +std::string as(const Json::Value &o) { + check(o.isString()); + return o.asString(); +} + +template +std::vector asVec(const Json::Value &o) { + check(o.isArray()); + std::vector res; + res.reserve(o.size()); + for (Json::ArrayIndex i = 0; i < o.size(); ++i) { + res.push_back(as(o[i])); + } + return res; +} + +template +std::array asArr(const Json::Value &o) { + check(o.isArray()); + check(o.size() == n); + std::array res; + for (Json::ArrayIndex i = 0; i < n; ++i) { + res[i] = as(o[i]); + } + return res; +} + +struct AccessorSparseIndices { + std::size_t bufferView; + std::size_t byteOffset; + // as defined in the glTF specification + enum class ComponentType { + UNSIGNED_BYTE, + UNSIGNED_SHORT, + UNSIGNED_INT, + }; + ComponentType componentType; + std::size_t componentSize() const { + switch (componentType) { + case ComponentType::UNSIGNED_BYTE: + return 1; + case ComponentType::UNSIGNED_SHORT: + return 2; + case ComponentType::UNSIGNED_INT: + return 4; + } + throw std::logic_error("invalid component type"); + } + std::size_t elementSize() const { + return componentSize(); + } + AccessorSparseIndices(const Json::Value &o) + : bufferView(as(o["bufferView"])) + , byteOffset(0) + { + check(o.isObject()); + if (o.isMember("byteOffset")) { + byteOffset = as(o["byteOffset"]); + check(byteOffset >= 0); + } + { + static std::unordered_map map = { + {5121, ComponentType::UNSIGNED_BYTE}, + {5123, ComponentType::UNSIGNED_SHORT}, + {5125, ComponentType::UNSIGNED_INT}, + }; + const auto &v = o["componentType"]; check(v.isUInt64()); + componentType = map.at(v.asUInt64()); + } + } +}; +template<> AccessorSparseIndices as(const Json::Value &o) { return o; } + +struct AccessorSparseValues { + std::size_t bufferView; + std::size_t byteOffset; + AccessorSparseValues(const Json::Value &o) + : bufferView(as(o["bufferView"])) + , byteOffset(0) + { + check(o.isObject()); + if (o.isMember("byteOffset")) { + byteOffset = as(o["byteOffset"]); + check(byteOffset >= 0); + } + } +}; +template<> AccessorSparseValues as(const Json::Value &o) { return o; } + +struct AccessorSparse { + std::size_t count; + AccessorSparseIndices indices; + AccessorSparseValues values; + AccessorSparse(const Json::Value &o) + : count(as(o["count"])) + , indices(as(o["indices"])) + , values(as(o["values"])) + { + check(o.isObject()); + check(count >= 1); + } +}; +template<> AccessorSparse as(const Json::Value &o) { return o; } + +struct Accessor { + std::optional bufferView; + std::size_t byteOffset; + // as defined in the glTF specification + enum class ComponentType { + BYTE, + UNSIGNED_BYTE, + SHORT, + UNSIGNED_SHORT, + UNSIGNED_INT, + FLOAT, + }; + ComponentType componentType; + std::size_t componentSize() const { + switch (componentType) { + case ComponentType::BYTE: + case ComponentType::UNSIGNED_BYTE: + return 1; + case ComponentType::SHORT: + case ComponentType::UNSIGNED_SHORT: + return 2; + case ComponentType::UNSIGNED_INT: + case ComponentType::FLOAT: + return 4; + } + throw std::logic_error("invalid component type"); + } + std::size_t count; + std::optional> max; + std::optional> min; + std::optional name; + bool normalized; + std::optional sparse; + enum class Type { + MAT2, + MAT3, + MAT4, + SCALAR, + VEC2, + VEC3, + VEC4, + }; + std::size_t typeCount() const { + switch (type) { + case Type::SCALAR: + return 1; + case Type::VEC2: + return 2; + case Type::VEC3: + return 3; + case Type::MAT2: + case Type::VEC4: + return 4; + case Type::MAT3: + return 9; + case Type::MAT4: + return 16; + } + throw std::logic_error("invalid type"); + } + Type type; + std::size_t elementSize() const { + return componentSize() * typeCount(); + } + Accessor(const Json::Value &o) + : byteOffset(0) + , count(as(o["count"])) + , normalized(false) + { + check(o.isObject()); + if (o.isMember("bufferView")) { + bufferView = as(o["bufferView"]); + } + { + static std::unordered_map map = { + {5120, ComponentType::BYTE}, + {5121, ComponentType::UNSIGNED_BYTE}, + {5122, ComponentType::SHORT}, + {5123, ComponentType::UNSIGNED_SHORT}, + {5125, ComponentType::UNSIGNED_INT}, + {5126, ComponentType::FLOAT}, + }; + const auto &v = o["componentType"]; check(v.isUInt64()); + componentType = map.at(v.asUInt64()); + } + if (o.isMember("byteOffset")) { + byteOffset = as(o["byteOffset"]); + check(byteOffset >= 0); + check(byteOffset % componentSize() == 0); + } + check(count >= 1); + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("normalized")) { + normalized = as(o["normalized"]); + } + if (o.isMember("sparse")) { + sparse = as(o["sparse"]); + } + { + static std::unordered_map map = { + {"MAT2", Type::MAT2}, + {"MAT3", Type::MAT3}, + {"MAT4", Type::MAT4}, + {"SCALAR", Type::SCALAR}, + {"VEC2", Type::VEC2}, + {"VEC3", Type::VEC3}, + {"VEC4", Type::VEC4}, + }; + const auto &v = o["type"]; check(v.isString()); + type = map.at(v.asString()); + } + if (o.isMember("max")) { + max = asVec(o["max"]); + check(max->size() == typeCount()); + } + if (o.isMember("min")) { + min = asVec(o["min"]); + check(min->size() == typeCount()); + } + } +}; +template<> Accessor as(const Json::Value &o) { return o; } + +struct AnimationChannelTarget { + std::optional node; + enum class Path { + ROTATION, + SCALE, + TRANSLATION, + WEIGHTS, + }; + Path path; + AnimationChannelTarget(const Json::Value &o) + { + check(o.isObject()); + if (o.isMember("node")) { + node = as(o["node"]); + } + { + static std::unordered_map map = { + {"rotation", Path::ROTATION}, + {"scale", Path::SCALE}, + {"translation", Path::TRANSLATION}, + {"weights", Path::WEIGHTS}, + }; + const auto &v = o["path"]; check(v.isString()); + path = map.at(v.asString()); + } + } +}; +template<> AnimationChannelTarget as(const Json::Value &o) { return o; } + +struct AnimationChannel { + std::size_t sampler; + AnimationChannelTarget target; + AnimationChannel(const Json::Value &o) + : sampler(as(o["sampler"])) + , target(as(o["target"])) + { + check(o.isObject()); + } +}; +template<> AnimationChannel as(const Json::Value &o) { return o; } + +struct AnimationSampler { + std::size_t input; + enum class Interpolation { + CUBICSPLINE, + LINEAR, + STEP, + }; + Interpolation interpolation; + std::size_t output; + AnimationSampler(const Json::Value &o) + : input(as(o["input"])) + , interpolation(Interpolation::LINEAR) + , output(as(o["output"])) + { + check(o.isObject()); + if (o.isMember("interpolation")) { + static std::unordered_map map = { + {"CUBICSPLINE", Interpolation::CUBICSPLINE}, + {"LINEAR", Interpolation::LINEAR}, + {"STEP", Interpolation::STEP}, + }; + const auto &v = o["interpolation"]; check(v.isString()); + interpolation = map.at(v.asString()); + } + } +}; +template<> AnimationSampler as(const Json::Value &o) { return o; } + +struct Animation { + std::vector channels; + std::optional name; + std::vector samplers; + Animation(const Json::Value &o) + : channels(asVec(o["channels"])) + , samplers(asVec(o["samplers"])) + { + check(o.isObject()); + check(channels.size() >= 1); + if (o.isMember("name")) { + name = as(o["name"]); + } + check(samplers.size() >= 1); + } +}; +template<> Animation as(const Json::Value &o) { return o; } + +struct Asset { + std::optional copyright; + std::optional generator; + std::optional minVersion; + std::string version; + Asset(const Json::Value &o) + : version(as(o["version"])) + { + check(o.isObject()); + if (o.isMember("copyright")) { + copyright = as(o["copyright"]); + } + if (o.isMember("generator")) { + generator = as(o["generator"]); + } + if (o.isMember("minVersion")) { + minVersion = as(o["minVersion"]); + } + } +}; +template<> Asset as(const Json::Value &o) { return o; } + +struct BufferView { + std::size_t buffer; + std::size_t byteLength; + std::size_t byteOffset; + std::optional byteStride; + std::optional name; + enum class Target { + ARRAY_BUFFER, + ELEMENT_ARRAY_BUFFER, + }; + std::optional target; + BufferView(const Json::Value &o) + : buffer(as(o["buffer"])) + , byteLength(as(o["byteLength"])) + , byteOffset(0) + { + check(o.isObject()); + check(byteLength >= 1); + if (o.isMember("byteOffset")) { + byteOffset = as(o["byteOffset"]); + check(byteOffset >= 0); + } + if (o.isMember("byteStride")) { + byteStride = as(o["byteStride"]); + check(byteStride.value() >= 4); + check(byteStride.value() <= 252); + check(byteStride.value() % 4 == 0); + } + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("target")) { + static std::unordered_map map = { + {34962, Target::ARRAY_BUFFER}, + {34963, Target::ELEMENT_ARRAY_BUFFER}, + }; + const auto &v = o["target"]; check(v.isUInt64()); + target = map.at(v.asUInt64()); + } + } +}; +template<> BufferView as(const Json::Value &o) { return o; } + +struct Buffer { + std::size_t byteLength; + std::optional name; + std::string data; + Buffer(const Json::Value &o, + const std::function &resolveURI) + : byteLength(as(o["byteLength"])) + { + check(o.isObject()); + check(byteLength >= 1); + if (o.isMember("name")) { + name = as(o["name"]); + } + check(o.isMember("uri")); + bool dataURI = false; + const std::string uri = as(o["uri"]); + for (auto &prefix : std::array { + "data:application/octet-stream;base64,", + "data:application/gltf-buffer;base64," + }) { + if (std::string_view(uri).substr(0, prefix.length()) == prefix) { + auto view = std::string_view(uri).substr(prefix.length()); + check(base64_is_valid(view)); + data = base64_decode(view); + dataURI = true; + break; + } + } + if (!dataURI) + data = resolveURI(uri); + check(data.size() >= byteLength); + data.resize(byteLength); + } +}; + +struct CameraOrthographic { + double xmag; + double ymag; + double zfar; + double znear; + CameraOrthographic(const Json::Value &o) + : xmag(as(o["xmag"])) + , ymag(as(o["ymag"])) + , zfar(as(o["zfar"])) + , znear(as(o["znear"])) + { + check(o.isObject()); + check(zfar > 0); + check(znear >= 0); + } +}; +template<> CameraOrthographic as(const Json::Value &o) { return o; } + +struct CameraPerspective { + std::optional aspectRatio; + double yfov; + std::optional zfar; + double znear; + CameraPerspective(const Json::Value &o) + : yfov(as(o["yfov"])) + , znear(as(o["znear"])) + { + check(o.isObject()); + if (o.isMember("aspectRatio")) { + aspectRatio = as(o["aspectRatio"]); + check(aspectRatio.value() > 0); + } + check(yfov > 0); + if (o.isMember("zfar")) { + zfar = as(o["zfar"]); + check(zfar.value() > 0); + } + check(znear > 0); + } +}; +template<> CameraPerspective as(const Json::Value &o) { return o; } + +struct Camera { + std::optional name; + std::optional orthographic; + std::optional perspective; + enum class Type { + ORTHOGRAPHIC, + PERSPECTIVE, + }; + Type type; + Camera(const Json::Value &o) + { + check(o.isObject()); + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("orthographic")) { + orthographic = as(o["orthographic"]); + } + if (o.isMember("perspective")) { + perspective = as(o["perspective"]); + } + { + static std::unordered_map map = { + {"orthographic", Type::ORTHOGRAPHIC}, + {"perspective", Type::PERSPECTIVE}, + }; + const auto &v = o["type"]; check(v.isString()); + type = map.at(v.asString()); + } + } +}; +template<> Camera as(const Json::Value &o) { return o; } + +struct Image { + std::optional bufferView; + enum class MimeType { + IMAGE_JPEG, + IMAGE_PNG, + }; + std::optional mimeType; + std::optional name; + std::optional uri; + Image(const Json::Value &o) + { + check(o.isObject()); + if (o.isMember("bufferView")) { + bufferView = as(o["bufferView"]); + } + if (o.isMember("mimeType")) { + static std::unordered_map map = { + {"image/jpeg", MimeType::IMAGE_JPEG}, + {"image/png", MimeType::IMAGE_PNG}, + }; + const auto &v = o["mimeType"]; check(v.isString()); + mimeType = map.at(v.asString()); + } + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("uri")) { + uri = as(o["uri"]); + } + } +}; +template<> Image as(const Json::Value &o) { return o; } + +struct TextureInfo { + std::size_t index; + std::size_t texCoord; + TextureInfo(const Json::Value &o) + : index(as(o["index"])) + , texCoord(0) + { + check(o.isObject()); + if (o.isMember("texCoord")) { + texCoord = as(o["texCoord"]); + check(texCoord >= 0); + } + } +}; +template<> TextureInfo as(const Json::Value &o) { return o; } + +struct MaterialNormalTextureInfo { + std::size_t index; + double scale; + std::size_t texCoord; + MaterialNormalTextureInfo(const Json::Value &o) + : index(as(o["index"])) + , scale(1) + , texCoord(0) + { + check(o.isObject()); + if (o.isMember("scale")) { + scale = as(o["scale"]); + } + if (o.isMember("texCoord")) { + texCoord = as(o["texCoord"]); + } + } +}; +template<> MaterialNormalTextureInfo as(const Json::Value &o) { return o; } + +struct MaterialOcclusionTextureInfo { + std::size_t index; + double strength; + std::size_t texCoord; + MaterialOcclusionTextureInfo(const Json::Value &o) + : index(as(o["index"])) + , strength(1) + , texCoord(0) + { + check(o.isObject()); + if (o.isMember("strength")) { + strength = as(o["strength"]); + check(strength >= 0); + check(strength <= 1); + } + if (o.isMember("texCoord")) { + texCoord = as(o["texCoord"]); + } + } +}; +template<> MaterialOcclusionTextureInfo as(const Json::Value &o) { return o; } + +struct MaterialPbrMetallicRoughness { + std::array baseColorFactor; + std::optional baseColorTexture; + double metallicFactor; + std::optional metallicRoughnessTexture; + double roughnessFactor; + MaterialPbrMetallicRoughness(const Json::Value &o) + : baseColorFactor{1, 1, 1, 1} + , metallicFactor(1) + , roughnessFactor(1) + { + check(o.isObject()); + if (o.isMember("baseColorFactor")) { + baseColorFactor = asArr(o["baseColorFactor"]); + for (auto v: baseColorFactor) { + check(v >= 0); + check(v <= 1); + } + } + if (o.isMember("baseColorTexture")) { + baseColorTexture = as(o["baseColorTexture"]); + } + if (o.isMember("metallicFactor")) { + metallicFactor = as(o["metallicFactor"]); + check(metallicFactor >= 0); + check(metallicFactor <= 1); + } + if (o.isMember("metallicRoughnessTexture")) { + metallicRoughnessTexture = as(o["metallicRoughnessTexture"]); + } + if (o.isMember("roughnessFactor")) { + roughnessFactor = as(o["roughnessFactor"]); + check(roughnessFactor >= 0); + check(roughnessFactor <= 1); + } + } +}; +template<> MaterialPbrMetallicRoughness as(const Json::Value &o) { return o; } + +struct Material { + double alphaCutoff; + enum class AlphaMode { + BLEND, + MASK, + OPAQUE, + }; + AlphaMode alphaMode; + bool doubleSided; + std::array emissiveFactor; + std::optional emissiveTexture; + std::optional name; + std::optional normalTexture; + std::optional occlusionTexture; + std::optional pbrMetallicRoughness; + Material(const Json::Value &o) + : alphaCutoff(0.5) + , alphaMode(AlphaMode::OPAQUE) + , doubleSided(false) + , emissiveFactor{0, 0, 0} + { + check(o.isObject()); + if (o.isMember("alphaCutoff")) { + alphaCutoff = as(o["alphaCutoff"]); + check(alphaCutoff >= 0); + } + if (o.isMember("alphaMode")){ + static std::unordered_map map = { + {"BLEND", AlphaMode::BLEND}, + {"MASK", AlphaMode::MASK}, + {"OPAQUE", AlphaMode::OPAQUE}, + }; + const auto &v = o["alphaMode"]; check(v.isString()); + alphaMode = map.at(v.asString()); + } + if (o.isMember("doubleSided")) { + doubleSided = as(o["doubleSided"]); + } + if (o.isMember("emissiveFactor")) { + emissiveFactor = asArr(o["emissiveFactor"]); + for (const auto &v: emissiveFactor) { + check(v >= 0); + check(v <= 1); + } + } + if (o.isMember("emissiveTexture")) { + emissiveTexture = as(o["emissiveTexture"]); + } + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("normalTexture")) { + normalTexture = as(o["normalTexture"]); + } + if (o.isMember("occlusionTexture")) { + occlusionTexture = as(o["occlusionTexture"]); + } + if (o.isMember("pbrMetallicRoughness")) { + pbrMetallicRoughness = as(o["pbrMetallicRoughness"]); + } + } +}; +template<> Material as(const Json::Value &o) { return o; } + +struct MeshPrimitive { + static void enumeratedProps(const Json::Value &o, const std::string &name, std::optional> &attr) { + for (std::size_t i = 0;; ++i) { + const std::string s = name + "_" + std::to_string(i); + if (!o.isMember(s)) break; + if (i == 0) { + attr = std::vector(); + } + attr->push_back(as(o[s])); + } + } + struct Attributes { + std::optional position, normal, tangent; + std::optional> texcoord, color, joints, weights; + Attributes(const Json::Value &o) { + if (o.isMember("POSITION")) + position = as(o["POSITION"]); + if (o.isMember("NORMAL")) + normal = as(o["NORMAL"]); + if (o.isMember("TANGENT")) + tangent = as(o["TANGENT"]); + enumeratedProps(o, "TEXCOORD", texcoord); + enumeratedProps(o, "COLOR", color); + enumeratedProps(o, "JOINTS", joints); + enumeratedProps(o, "WEIGHTS", weights); + check(joints.has_value() == weights.has_value()); + if (joints.has_value()) { + check(joints->size() == weights->size()); + } + check(position.has_value() + || normal.has_value() + || tangent.has_value() + || texcoord.has_value() + || color.has_value() + || joints.has_value() + || weights.has_value()); + } + }; + Attributes attributes; + std::optional indices; + std::optional material; + enum class Mode { + POINTS, + LINES, + LINE_LOOP, + LINE_STRIP, + TRIANGLES, + TRIANGLE_STRIP, + TRIANGLE_FAN, + }; + Mode mode; + struct MorphTargets { + std::optional position, normal, tangent; + std::optional> texcoord, color; + MorphTargets(const Json::Value &o) { + if (o.isMember("POSITION")) + position = as(o["POSITION"]); + if (o.isMember("NORMAL")) + normal = as(o["NORMAL"]); + if (o.isMember("TANGENT")) + tangent = as(o["TANGENT"]); + enumeratedProps(o, "TEXCOORD", texcoord); + enumeratedProps(o, "COLOR", color); + check(position.has_value() + || normal.has_value() + || tangent.has_value() + || texcoord.has_value() + || color.has_value()); + } + }; + std::optional> targets; + MeshPrimitive(const Json::Value &o) + : attributes(Attributes(o["attributes"])) + , mode(Mode::TRIANGLES) + { + check(o.isObject()); + if (o.isMember("indices")) { + indices = as(o["indices"]); + } + if (o.isMember("material")) { + material = as(o["material"]); + } + if (o.isMember("mode")) { + static std::unordered_map map = { + {0, Mode::POINTS}, + {1, Mode::LINES}, + {2, Mode::LINE_LOOP}, + {3, Mode::LINE_STRIP}, + {4, Mode::TRIANGLES}, + {5, Mode::TRIANGLE_STRIP}, + {6, Mode::TRIANGLE_FAN}, + }; + const auto &v = o["mode"]; check(v.isUInt64()); + mode = map.at(v.asUInt64()); + } + if (o.isMember("targets")) { + targets = asVec(o["targets"]); + check(targets->size() >= 1); + } + } +}; +template<> MeshPrimitive::MorphTargets as(const Json::Value &o) { return o; } +template<> MeshPrimitive as(const Json::Value &o) { return o; } + +struct Mesh { + std::optional name; + std::vector primitives; + std::optional> weights; + Mesh(const Json::Value &o) + : primitives(asVec(o["primitives"])) + { + check(o.isObject()); + if (o.isMember("name")) { + name = as(o["name"]); + } + check(primitives.size() >= 1); + if (o.isMember("weights")) { + weights = asVec(o["weights"]); + check(weights->size() >= 1); + } + } +}; +template<> Mesh as(const Json::Value &o) { return o; } + +struct Node { + std::optional camera; + std::optional> children; + typedef std::array Matrix; + struct TRS { + std::array translation = {0, 0, 0}; + std::array rotation = {0, 0, 0, 1}; + std::array scale = {1, 1, 1}; + }; + std::variant transform; + std::optional mesh; + std::optional name; + std::optional skin; + std::optional> weights; + Node(const Json::Value &o) + : transform(Matrix { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }) + { + check(o.isObject()); + if (o.isMember("camera")) { + camera = as(o["camera"]); + } + if (o.isMember("children")) { + children = asVec(o["children"]); + check(children->size() >= 1); + checkDuplicateFree(*children); + } + bool hasTRS = o.isMember("translation") || o.isMember("rotation") || o.isMember("scale"); + if (o.isMember("matrix")) { + check(!hasTRS); + transform = asArr(o["matrix"]); + } else if (hasTRS) { + TRS trs; + if (o.isMember("translation")) { + trs.translation = asArr(o["translation"]); + } + if (o.isMember("rotation")) { + trs.rotation = asArr(o["rotation"]); + for (auto v: trs.rotation) { + check(v >= -1); + check(v <= 1); + } + } + if (o.isMember("scale")) { + trs.scale = asArr(o["scale"]); + } + transform = trs; + } + if (o.isMember("mesh")) { + mesh = as(o["mesh"]); + } + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("skin")) { + check(mesh.has_value()); + skin = as(o["skin"]); + } + if (o.isMember("weights")) { + weights = asVec(o["weights"]); + check(weights->size() >= 1); + } + } +}; +template<> Node as(const Json::Value &o) { return o; } + +struct Sampler { + enum class MagFilter { + NEAREST, + LINEAR, + }; + std::optional magFilter; + enum class MinFilter { + NEAREST, + LINEAR, + NEAREST_MIPMAP_NEAREST, + LINEAR_MIPMAP_NEAREST, + NEAREST_MIPMAP_LINEAR, + LINEAR_MIPMAP_LINEAR, + }; + std::optional minFilter; + std::optional name; + enum class WrapS { + REPEAT, + CLAMP_TO_EDGE, + MIRRORED_REPEAT, + }; + WrapS wrapS; + enum class WrapT { + REPEAT, + CLAMP_TO_EDGE, + MIRRORED_REPEAT, + }; + WrapT wrapT; + Sampler(const Json::Value &o) + : wrapS(WrapS::REPEAT) + , wrapT(WrapT::REPEAT) + { + check(o.isObject()); + if (o.isMember("magFilter")) { + static std::unordered_map map = { + {9728, MagFilter::NEAREST}, + {9729, MagFilter::LINEAR}, + }; + const auto &v = o["magFilter"]; check(v.isUInt64()); + magFilter = map.at(v.asUInt64()); + } + if (o.isMember("minFilter")) { + static std::unordered_map map = { + {9728, MinFilter::NEAREST}, + {9729, MinFilter::LINEAR}, + {9984, MinFilter::NEAREST_MIPMAP_NEAREST}, + {9985, MinFilter::LINEAR_MIPMAP_NEAREST}, + {9986, MinFilter::NEAREST_MIPMAP_LINEAR}, + {9987, MinFilter::LINEAR_MIPMAP_LINEAR}, + }; + const auto &v = o["minFilter"]; check(v.isUInt64()); + minFilter = map.at(v.asUInt64()); + } + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("wrapS")) { + static std::unordered_map map = { + {10497, WrapS::REPEAT}, + {33071, WrapS::CLAMP_TO_EDGE}, + {33648, WrapS::MIRRORED_REPEAT}, + }; + const auto &v = o["wrapS"]; check(v.isUInt64()); + wrapS = map.at(v.asUInt64()); + } + if (o.isMember("wrapT")) { + static std::unordered_map map = { + {10497, WrapT::REPEAT}, + {33071, WrapT::CLAMP_TO_EDGE}, + {33648, WrapT::MIRRORED_REPEAT}, + }; + const auto &v = o["wrapT"]; check(v.isUInt64()); + wrapT = map.at(v.asUInt64()); + } + } +}; +template<> Sampler as(const Json::Value &o) { return o; } + +struct Scene { + std::optional name; + std::optional> nodes; + Scene(const Json::Value &o) + { + check(o.isObject()); + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("nodes")) { + nodes = asVec(o["nodes"]); + check(nodes->size() >= 1); + checkDuplicateFree(*nodes); + } + } +}; +template<> Scene as(const Json::Value &o) { return o; } + +struct Skin { + std::optional inverseBindMatrices; + std::vector joints; + std::optional name; + std::optional skeleton; + Skin(const Json::Value &o) + : joints(asVec(o["joints"])) + { + check(o.isObject()); + if (o.isMember("inverseBindMatrices")) { + inverseBindMatrices = as(o["inverseBindMatrices"]); + } + check(joints.size() >= 1); + checkDuplicateFree(joints); + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("skeleton")) { + skeleton = as(o["skeleton"]); + } + } +}; +template<> Skin as(const Json::Value &o) { return o; } + +struct Texture { + std::optional name; + std::optional sampler; + std::optional source; + Texture(const Json::Value &o) + { + check(o.isObject()); + if (o.isMember("name")) { + name = as(o["name"]); + } + if (o.isMember("sampler")) { + sampler = as(o["sampler"]); + } + if (o.isMember("source")) { + source = as(o["source"]); + } + } +}; +template<> Texture as(const Json::Value &o) { return o; } + +struct GlTF { + std::optional> accessors; + std::optional> animations; + Asset asset; + std::optional> bufferViews; + std::optional> buffers; + std::optional> cameras; + std::optional> extensionsRequired; + std::optional> extensionsUsed; + std::optional> images; + std::optional> materials; + std::optional> meshes; + std::optional> nodes; + std::optional> samplers; + std::optional scene; + std::optional> scenes; + std::optional> skins; + std::optional> textures; + static std::string uriError(const std::string &uri) { + // only base64 data URI support by default + throw std::runtime_error("unsupported URI: " + uri); + } + GlTF(const Json::Value &o, + const std::function &resolveURI = uriError) + : asset(as(o["asset"])) + { + check(o.isObject()); + if (o.isMember("accessors")) { + accessors = asVec(o["accessors"]); + check(accessors->size() >= 1); + } + if (o.isMember("animations")) { + animations = asVec(o["animations"]); + check(animations->size() >= 1); + } + if (o.isMember("bufferViews")) { + bufferViews = asVec(o["bufferViews"]); + check(bufferViews->size() >= 1); + } + if (o.isMember("buffers")) { + auto b = o["buffers"]; + check(b.isArray()); + std::vector bufs; + bufs.reserve(b.size()); + for (Json::ArrayIndex i = 0; i < b.size(); ++i) { + bufs.emplace_back(b[i], resolveURI); + } + check(bufs.size() >= 1); + buffers = std::move(bufs); + } + if (o.isMember("cameras")) { + cameras = asVec(o["cameras"]); + check(cameras->size() >= 1); + } + if (o.isMember("extensionsRequired")) { + extensionsRequired = asVec(o["extensionsRequired"]); + check(extensionsRequired->size() >= 1); + checkDuplicateFree(*extensionsRequired); + } + if (o.isMember("extensionsUsed")) { + extensionsUsed = asVec(o["extensionsUsed"]); + check(extensionsUsed->size() >= 1); + checkDuplicateFree(*extensionsUsed); + } + if (o.isMember("images")) { + images = asVec(o["images"]); + check(images->size() >= 1); + } + if (o.isMember("materials")) { + materials = asVec(o["materials"]); + check(materials->size() >= 1); + } + if (o.isMember("meshes")) { + meshes = asVec(o["meshes"]); + check(meshes->size() >= 1); + } + if (o.isMember("nodes")) { + nodes = asVec(o["nodes"]); + check(nodes->size() >= 1); + // Nodes must be a forest: + // 1. Each node should have indegree 0 or 1: + std::vector indeg(nodes->size()); + for (std::size_t i = 0; i < nodes->size(); ++i) { + auto children = nodes->at(i).children; + if (!children.has_value()) continue; + for (auto child : children.value()) { + ++indeg.at(child); + } + } + for (const auto deg : indeg) { + check(deg <= 1); + } + // 2. There should be no cycles: + std::vector visited(nodes->size()); + std::stack> toVisit; + for (std::size_t i = 0; i < nodes->size(); ++i) { + // Only start DFS in roots. + if (indeg[i] > 0) + continue; + + toVisit.push(i); + do { + std::size_t j = toVisit.top(); + check(!visited.at(j)); + visited[j] = true; + toVisit.pop(); + auto children = nodes->at(j).children; + if (!children.has_value()) + continue; + for (auto child : *children) { + toVisit.push(child); + } + } while (!toVisit.empty()); + } + } + if (o.isMember("samplers")) { + samplers = asVec(o["samplers"]); + check(samplers->size() >= 1); + } + if (o.isMember("scene")) { + scene = as(o["scene"]); + } + if (o.isMember("scenes")) { + scenes = asVec(o["scenes"]); + check(scenes->size() >= 1); + } + if (o.isMember("skins")) { + skins = asVec(o["skins"]); + check(skins->size() >= 1); + } + if (o.isMember("textures")) { + textures = asVec(o["textures"]); + check(textures->size() >= 1); + } + + // Validation + + checkForall(bufferViews, [&](const BufferView &view) { + check(buffers.has_value()); + const Buffer &buf = buffers->at(view.buffer); + // Be careful because of possible integer overflows. + check(view.byteOffset < buf.byteLength); + check(view.byteLength <= buf.byteLength); + check(view.byteOffset <= buf.byteLength - view.byteLength); + }); + + const auto checkAccessor = [&](const auto &accessor, + std::size_t bufferView, std::size_t byteOffset, std::size_t count) { + const BufferView &view = bufferViews->at(bufferView); + if (view.byteStride.has_value()) + check(*view.byteStride % accessor.componentSize() == 0); + check(byteOffset < view.byteLength); + // Use division to avoid overflows. + const auto effective_byte_stride = view.byteStride.value_or(accessor.elementSize()); + check(count <= (view.byteLength - byteOffset) / effective_byte_stride); + }; + checkForall(accessors, [&](const Accessor &accessor) { + if (accessor.bufferView.has_value()) + checkAccessor(accessor, *accessor.bufferView, accessor.byteOffset, accessor.count); + if (accessor.sparse.has_value()) { + const auto &indices = accessor.sparse->indices; + checkAccessor(indices, indices.bufferView, indices.byteOffset, accessor.sparse->count); + const auto &values = accessor.sparse->values; + checkAccessor(accessor, values.bufferView, values.byteOffset, accessor.sparse->count); + } + }); + + checkForall(images, [&](const Image &image) { + checkIndex(bufferViews, image.bufferView); + }); + + checkForall(meshes, [&](const Mesh &mesh) { + for (const auto &primitive : mesh.primitives) { + checkIndex(accessors, primitive.indices); + checkIndex(materials, primitive.material); + checkIndex(accessors, primitive.attributes.normal); + checkIndex(accessors, primitive.attributes.position); + checkIndex(accessors, primitive.attributes.tangent); + checkForall(primitive.attributes.texcoord, [&](const std::size_t &i) { + checkIndex(accessors, i); + }); + checkForall(primitive.attributes.color, [&](const std::size_t &i) { + checkIndex(accessors, i); + }); + checkForall(primitive.attributes.joints, [&](const std::size_t &i) { + checkIndex(accessors, i); + }); + checkForall(primitive.attributes.weights, [&](const std::size_t &i) { + checkIndex(accessors, i); + }); + if (primitive.material.has_value()) { + const Material &material = materials->at(primitive.material.value()); + if (material.emissiveTexture.has_value()) { + check(primitive.attributes.texcoord.has_value()); + check(material.emissiveTexture->texCoord < primitive.attributes.texcoord->size()); + } + if (material.normalTexture.has_value()) { + check(primitive.attributes.texcoord.has_value()); + check(material.normalTexture->texCoord < primitive.attributes.texcoord->size()); + } + if (material.occlusionTexture.has_value()) { + check(primitive.attributes.texcoord.has_value()); + check(material.occlusionTexture->texCoord < primitive.attributes.texcoord->size()); + } + } + checkForall(primitive.targets, [&](const MeshPrimitive::MorphTargets &target) { + checkIndex(accessors, target.normal); + checkIndex(accessors, target.position); + checkIndex(accessors, target.tangent); + checkForall(target.texcoord, [&](const std::size_t &i) { + checkIndex(accessors, i); + }); + checkForall(target.color, [&](const std::size_t &i) { + checkIndex(accessors, i); + }); + }); + } + }); + + checkForall(nodes, [&](const Node &node) { + checkIndex(cameras, node.camera); + checkIndex(meshes, node.mesh); + checkIndex(skins, node.skin); + }); + + checkForall(scenes, [&](const Scene &scene) { + checkForall(scene.nodes, [&](const size_t &i) { + checkIndex(nodes, i); + }); + }); + + checkForall(skins, [&](const Skin &skin) { + checkIndex(accessors, skin.inverseBindMatrices); + for (const std::size_t &i : skin.joints) + checkIndex(nodes, i); + checkIndex(nodes, skin.skeleton); + }); + + checkForall(textures, [&](const Texture &texture) { + checkIndex(samplers, texture.sampler); + checkIndex(images, texture.source); + }); + + checkForall(animations, [&](const Animation &animation) { + for (const auto &sampler : animation.samplers) { + checkIndex(accessors, sampler.input); + const auto &accessor = accessors->at(sampler.input); + check(accessor.type == Accessor::Type::SCALAR); + check(accessor.componentType == Accessor::ComponentType::FLOAT); + checkIndex(accessors, sampler.output); + } + for (const auto &channel : animation.channels) { + checkIndex(nodes, channel.target.node); + checkIndex(animation.samplers, channel.sampler); + } + }); + + checkIndex(scenes, scene); + } +}; + +} diff --git a/src/client/client.cpp b/src/client/client.cpp index 9c12be8f2..3720ec54f 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -826,7 +826,7 @@ bool Client::loadMedia(const std::string &data, const std::string &filename, } const char *model_ext[] = { - ".x", ".b3d", ".obj", + ".x", ".b3d", ".obj", ".gltf", NULL }; name = removeStringEnd(filename, model_ext); diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 19bee6f7f..563fe0abd 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -844,14 +844,19 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) if (m_animated_meshnode) { u32 mat_count = m_animated_meshnode->getMaterialCount(); + assert(mat_count == m_animated_meshnode->getMesh()->getMeshBufferCount()); + u32 max_tex_idx = 0; + for (u32 i = 0; i < mat_count; ++i) { + max_tex_idx = std::max(max_tex_idx, + m_animated_meshnode->getMesh()->getTextureSlot(i)); + } if (mat_count == 0 || m_prop.textures.empty()) { // nothing - } else if (mat_count > m_prop.textures.size()) { + } else if (max_tex_idx >= m_prop.textures.size()) { std::ostringstream oss; oss << "GenericCAO::addToScene(): Model " - << m_prop.mesh << " loaded with " << mat_count - << " mesh buffers but only " << m_prop.textures.size() - << " texture(s) specified, this is deprecated."; + << m_prop.mesh << " is missing " << (max_tex_idx + 1 - m_prop.textures.size()) + << " more texture(s), this is deprecated."; logOnce(oss, warningstream); video::ITexture *last = m_animated_meshnode->getMaterial(0).TextureLayers[0].Texture; @@ -1370,9 +1375,11 @@ void GenericCAO::updateTextures(std::string mod) else if (m_animated_meshnode) { if (m_prop.visual == "mesh") { - for (u32 i = 0; i < m_prop.textures.size() && - i < m_animated_meshnode->getMaterialCount(); ++i) { - std::string texturestring = m_prop.textures[i]; + for (u32 i = 0; i < m_animated_meshnode->getMaterialCount(); ++i) { + const auto texture_idx = m_animated_meshnode->getMesh()->getTextureSlot(i); + if (texture_idx >= m_prop.textures.size()) + continue; + std::string texturestring = m_prop.textures[texture_idx]; if (texturestring.empty()) continue; // Empty texture string means don't modify that material texturestring += mod; diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 1de36c9c5..4f4056668 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "content_mapblock.h" +#include "util/basic_macros.h" #include "util/numeric.h" #include "util/directiontables.h" #include "mapblock_mesh.h" @@ -1676,7 +1677,9 @@ void MapblockMeshGenerator::drawMeshNode() int mesh_buffer_count = mesh->getMeshBufferCount(); for (int j = 0; j < mesh_buffer_count; j++) { - useTile(j); + // Only up to 6 tiles are supported + const auto tile = mesh->getTextureSlot(j); + useTile(MYMIN(tile, 5)); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices(); int vertex_count = buf->getVertexCount(); diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 711f7e1c6..711712c33 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -397,8 +397,8 @@ scene::SMesh* cloneMesh(scene::IMesh *src_mesh) scene::IMeshBuffer *temp_buf = cloneMeshBuffer( src_mesh->getMeshBuffer(j)); dst_mesh->addMeshBuffer(temp_buf); + dst_mesh->setTextureSlot(j, src_mesh->getTextureSlot(j)); temp_buf->drop(); - } return dst_mesh; } diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 3f22fb3c4..1fd373b9c 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -2807,8 +2807,13 @@ void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element) auto meshnode = e->setMesh(mesh); - for (u32 i = 0; i < textures.size() && i < meshnode->getMaterialCount(); ++i) - e->setTexture(i, m_tsrc->getTexture(unescape_string(textures[i]))); + for (u32 i = 0; i < meshnode->getMaterialCount(); ++i) { + const auto texture_idx = mesh->getTextureSlot(i); + if (texture_idx >= textures.size()) + warningstream << "Invalid model element: Not enough textures" << std::endl; + else + e->setTexture(i, m_tsrc->getTexture(unescape_string(textures[texture_idx]))); + } if (vec_rot.size() >= 2) e->setRotation(v2f(stof(vec_rot[0]), stof(vec_rot[1]))); diff --git a/src/server.cpp b/src/server.cpp index 609b7188b..c76155015 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2465,7 +2465,7 @@ bool Server::addMediaFile(const std::string &filename, const char *supported_ext[] = { ".png", ".jpg", ".bmp", ".tga", ".ogg", - ".x", ".b3d", ".obj", + ".x", ".b3d", ".obj", ".gltf", // Custom translation file format ".tr", NULL diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index ec52ee6bf..93803c912 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -49,7 +49,7 @@ set (UNITTEST_CLIENT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_content_mapblock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_gltf_mesh_loader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp PARENT_SCOPE) - diff --git a/src/unittest/test_irr_gltf_mesh_loader.cpp b/src/unittest/test_irr_gltf_mesh_loader.cpp new file mode 100644 index 000000000..8ab57e590 --- /dev/null +++ b/src/unittest/test_irr_gltf_mesh_loader.cpp @@ -0,0 +1,366 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "CSceneManager.h" +#include "content/subgames.h" +#include "filesys.h" + +#include "CReadFile.h" +#include "irr_v3d.h" +#include "irr_v2d.h" + +#include + +#include "catch.h" + +TEST_CASE("gltf") { + +const auto gamespec = findSubgame("devtest"); + +if (!gamespec.isValid()) + SKIP(); + +irr::scene::CSceneManager smgr(nullptr, nullptr, nullptr); +const auto loadMesh = [&smgr](const irr::io::path& filepath) { + irr::io::CReadFile file(filepath); + return smgr.getMesh(&file); +}; + +const static auto model_stem = gamespec.gamemods_path + + DIR_DELIM + "gltf" + DIR_DELIM + "models" + DIR_DELIM + "gltf_"; + +SECTION("error cases") { + const static auto invalid_model_path = gamespec.gamemods_path + DIR_DELIM + "gltf" + DIR_DELIM + "invalid" + DIR_DELIM; + + SECTION("empty gltf file") { + CHECK(loadMesh(invalid_model_path + "empty.gltf") == nullptr); + } + + SECTION("null file pointer") { + CHECK(smgr.getMesh(nullptr) == nullptr); + } + + SECTION("invalid JSON") { + CHECK(loadMesh(invalid_model_path + "json_missing_brace.gltf") == nullptr); + } + + // This is an example of something that should be validated by tiniergltf. + SECTION("invalid bufferview bounds") + { + CHECK(loadMesh(invalid_model_path + "invalid_bufferview_bounds.gltf") == nullptr); + } +} + +SECTION("minimal triangle") { + const auto path = GENERATE( + model_stem + "minimal_triangle.gltf", + model_stem + "triangle_with_vertex_stride.gltf", + // Test non-indexed geometry. + model_stem + "triangle_without_indices.gltf"); + INFO(path); + const auto mesh = loadMesh(path); + REQUIRE(mesh != nullptr); + REQUIRE(mesh->getMeshBufferCount() == 1); + + SECTION("vertex coordinates are correct") { + REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 3); + auto vertices = static_cast( + mesh->getMeshBuffer(0)->getVertices()); + CHECK(vertices[0].Pos == v3f {0.0f, 0.0f, 0.0f}); + CHECK(vertices[1].Pos == v3f {1.0f, 0.0f, 0.0f}); + CHECK(vertices[2].Pos == v3f {0.0f, 1.0f, 0.0f}); + } + + SECTION("vertex indices are correct") { + REQUIRE(mesh->getMeshBuffer(0)->getIndexCount() == 3); + auto indices = static_cast( + mesh->getMeshBuffer(0)->getIndices()); + CHECK(indices[0] == 2); + CHECK(indices[1] == 1); + CHECK(indices[2] == 0); + } +} + +SECTION("blender cube") { + const auto mesh = loadMesh(model_stem + "blender_cube.gltf"); + REQUIRE(mesh != nullptr); + REQUIRE(mesh->getMeshBufferCount() == 1); + SECTION("vertex coordinates are correct") { + REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(0)->getVertices()); + CHECK(vertices[0].Pos == v3f{-10.0f, -10.0f, -10.0f}); + CHECK(vertices[3].Pos == v3f{-10.0f, 10.0f, -10.0f}); + CHECK(vertices[6].Pos == v3f{-10.0f, -10.0f, 10.0f}); + CHECK(vertices[9].Pos == v3f{-10.0f, 10.0f, 10.0f}); + CHECK(vertices[12].Pos == v3f{10.0f, -10.0f, -10.0f}); + CHECK(vertices[15].Pos == v3f{10.0f, 10.0f, -10.0f}); + CHECK(vertices[18].Pos == v3f{10.0f, -10.0f, 10.0f}); + CHECK(vertices[21].Pos == v3f{10.0f, 10.0f, 10.0f}); + } + + SECTION("vertex indices are correct") { + REQUIRE(mesh->getMeshBuffer(0)->getIndexCount() == 36); + auto indices = static_cast( + mesh->getMeshBuffer(0)->getIndices()); + CHECK(indices[0] == 16); + CHECK(indices[1] == 5); + CHECK(indices[2] == 22); + CHECK(indices[35] == 0); + } + + SECTION("vertex normals are correct") { + REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(0)->getVertices()); + CHECK(vertices[0].Normal == v3f{-1.0f, 0.0f, 0.0f}); + CHECK(vertices[1].Normal == v3f{0.0f, -1.0f, 0.0f}); + CHECK(vertices[2].Normal == v3f{0.0f, 0.0f, -1.0f}); + CHECK(vertices[3].Normal == v3f{-1.0f, 0.0f, 0.0f}); + CHECK(vertices[6].Normal == v3f{-1.0f, 0.0f, 0.0f}); + CHECK(vertices[23].Normal == v3f{1.0f, 0.0f, 0.0f}); + + } + + SECTION("texture coords are correct") { + REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(0)->getVertices()); + CHECK(vertices[0].TCoords == v2f{0.375f, 1.0f}); + CHECK(vertices[1].TCoords == v2f{0.125f, 0.25f}); + CHECK(vertices[2].TCoords == v2f{0.375f, 0.0f}); + CHECK(vertices[3].TCoords == v2f{0.6250f, 1.0f}); + CHECK(vertices[6].TCoords == v2f{0.375f, 0.75f}); + } +} + +SECTION("blender cube scaled") { + const auto mesh = loadMesh(model_stem + "blender_cube_scaled.gltf"); + REQUIRE(mesh != nullptr); + REQUIRE(mesh->getMeshBufferCount() == 1); + + SECTION("Scaling is correct") { + REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(0)->getVertices()); + + CHECK(vertices[0].Pos == v3f{-150.0f, -1.0f, -21.5f}); + CHECK(vertices[3].Pos == v3f{-150.0f, 1.0f, -21.5f}); + CHECK(vertices[6].Pos == v3f{-150.0f, -1.0f, 21.5f}); + CHECK(vertices[9].Pos == v3f{-150.0f, 1.0f, 21.5f}); + CHECK(vertices[12].Pos == v3f{150.0f, -1.0f, -21.5f}); + CHECK(vertices[15].Pos == v3f{150.0f, 1.0f, -21.5f}); + CHECK(vertices[18].Pos == v3f{150.0f, -1.0f, 21.5f}); + CHECK(vertices[21].Pos == v3f{150.0f, 1.0f, 21.5f}); + } +} + +SECTION("blender cube matrix transform") { + const auto mesh = loadMesh(model_stem + "blender_cube_matrix_transform.gltf"); + REQUIRE(mesh != nullptr); + REQUIRE(mesh->getMeshBufferCount() == 1); + + SECTION("Transformation is correct") { + REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(0)->getVertices()); + const auto checkVertex = [&](const std::size_t i, v3f vec) { + // The transform scales by (1, 2, 3) and translates by (4, 5, 6). + CHECK(vertices[i].Pos == vec * v3f{1, 2, 3} + // The -6 is due to the coordinate system conversion. + + v3f{4, 5, -6}); + }; + checkVertex(0, v3f{-1, -1, -1}); + checkVertex(3, v3f{-1, 1, -1}); + checkVertex(6, v3f{-1, -1, 1}); + checkVertex(9, v3f{-1, 1, 1}); + checkVertex(12, v3f{1, -1, -1}); + checkVertex(15, v3f{1, 1, -1}); + checkVertex(18, v3f{1, -1, 1}); + checkVertex(21, v3f{1, 1, 1}); + } +} + +SECTION("snow man") { + const auto mesh = loadMesh(model_stem + "snow_man.gltf"); + REQUIRE(mesh != nullptr); + REQUIRE(mesh->getMeshBufferCount() == 3); + + SECTION("vertex coordinates are correct for all buffers") { + REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24); + { + auto vertices = static_cast( + mesh->getMeshBuffer(0)->getVertices()); + CHECK(vertices[0].Pos == v3f{3.0f, 24.0f, -3.0f}); + CHECK(vertices[3].Pos == v3f{3.0f, 18.0f, 3.0f}); + CHECK(vertices[6].Pos == v3f{-3.0f, 18.0f, -3.0f}); + CHECK(vertices[9].Pos == v3f{3.0f, 24.0f, 3.0f}); + CHECK(vertices[12].Pos == v3f{3.0f, 18.0f, -3.0f}); + CHECK(vertices[15].Pos == v3f{-3.0f, 18.0f, 3.0f}); + CHECK(vertices[18].Pos == v3f{3.0f, 18.0f, -3.0f}); + CHECK(vertices[21].Pos == v3f{3.0f, 18.0f, 3.0f}); + } + { + REQUIRE(mesh->getMeshBuffer(1)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(1)->getVertices()); + CHECK(vertices[2].Pos == v3f{5.0f, 10.0f, 5.0f}); + CHECK(vertices[3].Pos == v3f{5.0f, 0.0f, 5.0f}); + CHECK(vertices[7].Pos == v3f{-5.0f, 0.0f, 5.0f}); + CHECK(vertices[8].Pos == v3f{5.0f, 10.0f, -5.0f}); + CHECK(vertices[14].Pos == v3f{5.0f, 0.0f, 5.0f}); + CHECK(vertices[16].Pos == v3f{5.0f, 10.0f, -5.0f}); + CHECK(vertices[22].Pos == v3f{-5.0f, 10.0f, 5.0f}); + CHECK(vertices[23].Pos == v3f{-5.0f, 0.0f, 5.0f}); + } + { + REQUIRE(mesh->getMeshBuffer(2)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(2)->getVertices()); + CHECK(vertices[1].Pos == v3f{4.0f, 10.0f, -4.0f}); + CHECK(vertices[2].Pos == v3f{4.0f, 18.0f, 4.0f}); + CHECK(vertices[3].Pos == v3f{4.0f, 10.0f, 4.0f}); + CHECK(vertices[10].Pos == v3f{-4.0f, 18.0f, -4.0f}); + CHECK(vertices[11].Pos == v3f{-4.0f, 18.0f, 4.0f}); + CHECK(vertices[12].Pos == v3f{4.0f, 10.0f, -4.0f}); + CHECK(vertices[17].Pos == v3f{-4.0f, 18.0f, -4.0f}); + CHECK(vertices[18].Pos == v3f{4.0f, 10.0f, -4.0f}); + } + } + + SECTION("vertex indices are correct for all buffers") { + { + REQUIRE(mesh->getMeshBuffer(0)->getIndexCount() == 36); + auto indices = static_cast( + mesh->getMeshBuffer(0)->getIndices()); + CHECK(indices[0] == 23); + CHECK(indices[1] == 21); + CHECK(indices[2] == 22); + CHECK(indices[35] == 2); + } + { + REQUIRE(mesh->getMeshBuffer(1)->getIndexCount() == 36); + auto indices = static_cast( + mesh->getMeshBuffer(1)->getIndices()); + CHECK(indices[10] == 16); + CHECK(indices[11] == 18); + CHECK(indices[15] == 13); + CHECK(indices[27] == 5); + } + { + REQUIRE(mesh->getMeshBuffer(2)->getIndexCount() == 36); + auto indices = static_cast( + mesh->getMeshBuffer(2)->getIndices()); + CHECK(indices[26] == 6); + CHECK(indices[27] == 5); + CHECK(indices[29] == 6); + CHECK(indices[32] == 2); + } + } + + + SECTION("vertex normals are correct for all buffers") { + { + REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(0)->getVertices()); + CHECK(vertices[0].Normal == v3f{1.0f, 0.0f, -0.0f}); + CHECK(vertices[1].Normal == v3f{1.0f, 0.0f, -0.0f}); + CHECK(vertices[2].Normal == v3f{1.0f, 0.0f, -0.0f}); + CHECK(vertices[3].Normal == v3f{1.0f, 0.0f, -0.0f}); + CHECK(vertices[6].Normal == v3f{-1.0f, 0.0f, -0.0f}); + CHECK(vertices[23].Normal == v3f{0.0f, 0.0f, 1.0f}); + } + { + REQUIRE(mesh->getMeshBuffer(1)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(1)->getVertices()); + CHECK(vertices[0].Normal == v3f{1.0f, 0.0f, -0.0f}); + CHECK(vertices[1].Normal == v3f{1.0f, 0.0f, -0.0f}); + CHECK(vertices[3].Normal == v3f{1.0f, 0.0f, -0.0f}); + CHECK(vertices[6].Normal == v3f{-1.0f, 0.0f, -0.0f}); + CHECK(vertices[7].Normal == v3f{-1.0f, 0.0f, -0.0f}); + CHECK(vertices[22].Normal == v3f{0.0f, 0.0f, 1.0f}); + } + { + REQUIRE(mesh->getMeshBuffer(2)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(2)->getVertices()); + CHECK(vertices[3].Normal == v3f{1.0f, 0.0f, -0.0f}); + CHECK(vertices[4].Normal == v3f{-1.0f, 0.0f, -0.0f}); + CHECK(vertices[5].Normal == v3f{-1.0f, 0.0f, -0.0f}); + CHECK(vertices[10].Normal == v3f{0.0f, 1.0f, -0.0f}); + CHECK(vertices[11].Normal == v3f{0.0f, 1.0f, -0.0f}); + CHECK(vertices[19].Normal == v3f{0.0f, 0.0f, -1.0f}); + } + } + + + SECTION("texture coords are correct for all buffers") { + { + REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(0)->getVertices()); + CHECK(vertices[0].TCoords == v2f{0.583333313f, 0.791666686f}); + CHECK(vertices[1].TCoords == v2f{0.583333313f, 0.666666686f}); + CHECK(vertices[2].TCoords == v2f{0.708333313f, 0.791666686f}); + CHECK(vertices[5].TCoords == v2f{0.375f, 0.416666657f}); + CHECK(vertices[6].TCoords == v2f{0.5f, 0.291666657f}); + CHECK(vertices[19].TCoords == v2f{0.708333313f, 0.75f}); + } + { + REQUIRE(mesh->getMeshBuffer(1)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(1)->getVertices()); + + CHECK(vertices[1].TCoords == v2f{0.0f, 0.791666686f}); + CHECK(vertices[4].TCoords == v2f{0.208333328f, 0.791666686f}); + CHECK(vertices[5].TCoords == v2f{0.0f, 0.791666686f}); + CHECK(vertices[6].TCoords == v2f{0.208333328f, 0.583333313f}); + CHECK(vertices[12].TCoords == v2f{0.416666657f, 0.791666686f}); + CHECK(vertices[15].TCoords == v2f{0.208333328f, 0.583333313f}); + } + { + REQUIRE(mesh->getMeshBuffer(2)->getVertexCount() == 24); + auto vertices = static_cast( + mesh->getMeshBuffer(2)->getVertices()); + CHECK(vertices[10].TCoords == v2f{0.375f, 0.416666657f}); + CHECK(vertices[11].TCoords == v2f{0.375f, 0.583333313f}); + CHECK(vertices[12].TCoords == v2f{0.708333313f, 0.625f}); + CHECK(vertices[17].TCoords == v2f{0.541666687f, 0.458333343f}); + CHECK(vertices[20].TCoords == v2f{0.208333328f, 0.416666657f}); + CHECK(vertices[22].TCoords == v2f{0.375f, 0.416666657f}); + } + } +} + +// https://github.com/KhronosGroup/glTF-Sample-Models/tree/main/2.0/SimpleSparseAccessor +SECTION("simple sparse accessor") +{ + const auto mesh = loadMesh(model_stem + "simple_sparse_accessor.gltf"); + REQUIRE(mesh != nullptr); + const auto *vertices = reinterpret_cast( + mesh->getMeshBuffer(0)->getVertices()); + const std::array expectedPositions = { + // Lower + v3f(0, 0, 0), + v3f(1, 0, 0), + v3f(2, 0, 0), + v3f(3, 0, 0), + v3f(4, 0, 0), + v3f(5, 0, 0), + v3f(6, 0, 0), + // Upper + v3f(0, 1, 0), + v3f(1, 2, 0), // overridden + v3f(2, 1, 0), + v3f(3, 3, 0), // overridden + v3f(4, 1, 0), + v3f(5, 4, 0), // overridden + v3f(6, 1, 0), + }; + for (std::size_t i = 0; i < expectedPositions.size(); ++i) + CHECK(vertices[i].Pos == expectedPositions[i]); +} + +} diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index 90c29e125..f26734ab3 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -122,7 +122,7 @@ void TestServerModManager::testGetMods() ServerModManager sm(m_worlddir); const auto &mods = sm.getMods(); // `ls ./games/devtest/mods | wc -l` + 1 (test mod) - UASSERTEQ(std::size_t, mods.size(), 31 + 1); + UASSERTEQ(std::size_t, mods.size(), 32 + 1); // Ensure we found basenodes mod (part of devtest) // and test_mod (for testing MINETEST_MOD_PATH). From b8b99d5cf1c430f373d9b384e5d57d08f823b809 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 31 Aug 2024 21:23:16 +0200 Subject: [PATCH 061/200] Use std::string_view in logging code --- src/log.cpp | 25 +++++++++++++------------ src/log.h | 32 ++++++++++++++++---------------- src/script/common/c_internal.cpp | 23 +++++++++++++---------- src/script/common/c_internal.h | 7 +++++-- src/script/lua_api/l_util.cpp | 12 ++++++------ src/terminal_chat_console.h | 6 +++--- src/util/stream.h | 10 +++++----- 7 files changed, 61 insertions(+), 54 deletions(-) diff --git a/src/log.cpp b/src/log.cpp index 98939c9bf..f7eb691ac 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -55,7 +55,7 @@ public: return m_logger.hasOutput(m_level); } - virtual void log(const std::string &buf) override { + virtual void log(std::string_view buf) override { if (!m_raw) { m_logger.log(m_level, buf); } else { @@ -106,7 +106,7 @@ thread_local LogStream dout_con(trace_target); // Android #ifdef __ANDROID__ -static unsigned int g_level_to_android[] = { +constexpr static unsigned int g_level_to_android[] = { ANDROID_LOG_INFO, // LL_NONE ANDROID_LOG_ERROR, // LL_ERROR ANDROID_LOG_WARN, // LL_WARNING @@ -116,11 +116,12 @@ static unsigned int g_level_to_android[] = { ANDROID_LOG_VERBOSE, // LL_TRACE }; -void AndroidLogOutput::logRaw(LogLevel lev, const std::string &line) +void AndroidLogOutput::logRaw(LogLevel lev, std::string_view line) { static_assert(ARRLEN(g_level_to_android) == LL_MAX, "mismatch between android and internal loglevels"); - __android_log_write(g_level_to_android[lev], PROJECT_NAME_C, line.c_str()); + __android_log_print(g_level_to_android[lev], PROJECT_NAME_C, "%.*s", + line.size(), line.data()); } #endif @@ -131,7 +132,7 @@ void AndroidLogOutput::logRaw(LogLevel lev, const std::string &line) //// Logger //// -LogLevel Logger::stringToLevel(const std::string &name) +LogLevel Logger::stringToLevel(std::string_view name) { if (name == "none") return LL_NONE; @@ -202,7 +203,7 @@ void Logger::setLevelSilenced(LogLevel lev, bool silenced) m_silenced_levels[lev] = silenced; } -void Logger::registerThread(const std::string &name) +void Logger::registerThread(std::string_view name) { std::thread::id id = std::this_thread::get_id(); MutexAutoLock lock(m_mutex); @@ -252,7 +253,7 @@ const std::string &Logger::getThreadName() return fallback_name; } -void Logger::log(LogLevel lev, const std::string &text) +void Logger::log(LogLevel lev, std::string_view text) { if (isLevelSilenced(lev)) return; @@ -268,7 +269,7 @@ void Logger::log(LogLevel lev, const std::string &text) logToOutputs(lev, line, timestamp, thread_name, text); } -void Logger::logRaw(LogLevel lev, const std::string &text) +void Logger::logRaw(LogLevel lev, std::string_view text) { if (isLevelSilenced(lev)) return; @@ -276,7 +277,7 @@ void Logger::logRaw(LogLevel lev, const std::string &text) logToOutputsRaw(lev, text); } -void Logger::logToOutputsRaw(LogLevel lev, const std::string &line) +void Logger::logToOutputsRaw(LogLevel lev, std::string_view line) { MutexAutoLock lock(m_mutex); for (size_t i = 0; i != m_outputs[lev].size(); i++) @@ -285,7 +286,7 @@ void Logger::logToOutputsRaw(LogLevel lev, const std::string &line) void Logger::logToOutputs(LogLevel lev, const std::string &combined, const std::string &time, const std::string &thread_name, - const std::string &payload_text) + std::string_view payload_text) { MutexAutoLock lock(m_mutex); for (size_t i = 0; i != m_outputs[lev].size(); i++) @@ -334,7 +335,7 @@ StreamLogOutput::StreamLogOutput(std::ostream &stream) : #endif } -void StreamLogOutput::logRaw(LogLevel lev, const std::string &line) +void StreamLogOutput::logRaw(LogLevel lev, std::string_view line) { bool colored_message = (Logger::color_mode == LOG_COLOR_ALWAYS) || (Logger::color_mode == LOG_COLOR_AUTO && is_tty); @@ -385,7 +386,7 @@ void LogOutputBuffer::updateLogLevel() m_logger.addOutputMaxLevel(this, log_level); } -void LogOutputBuffer::logRaw(LogLevel lev, const std::string &line) +void LogOutputBuffer::logRaw(LogLevel lev, std::string_view line) { std::string color; diff --git a/src/log.h b/src/log.h index 9ac4e5767..721ce58ed 100644 --- a/src/log.h +++ b/src/log.h @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include -#include +#include #include #include #include @@ -62,14 +62,14 @@ public: LogLevelMask removeOutput(ILogOutput *out); void setLevelSilenced(LogLevel lev, bool silenced); - void registerThread(const std::string &name); + void registerThread(std::string_view name); void deregisterThread(); - void log(LogLevel lev, const std::string &text); + void log(LogLevel lev, std::string_view text); // Logs without a prefix - void logRaw(LogLevel lev, const std::string &text); + void logRaw(LogLevel lev, std::string_view text); - static LogLevel stringToLevel(const std::string &name); + static LogLevel stringToLevel(std::string_view name); static const char *getLevelLabel(LogLevel lev); bool hasOutput(LogLevel level) { @@ -83,10 +83,10 @@ public: static LogColor color_mode; private: - void logToOutputsRaw(LogLevel, const std::string &line); + void logToOutputsRaw(LogLevel, std::string_view line); void logToOutputs(LogLevel, const std::string &combined, const std::string &time, const std::string &thread_name, - const std::string &payload_text); + std::string_view payload_text); const std::string &getThreadName(); @@ -99,17 +99,17 @@ private: class ILogOutput { public: - virtual void logRaw(LogLevel, const std::string &line) = 0; + virtual void logRaw(LogLevel, std::string_view line) = 0; virtual void log(LogLevel, const std::string &combined, const std::string &time, const std::string &thread_name, - const std::string &payload_text) = 0; + std::string_view payload_text) = 0; }; class ICombinedLogOutput : public ILogOutput { public: void log(LogLevel lev, const std::string &combined, const std::string &time, const std::string &thread_name, - const std::string &payload_text) + std::string_view payload_text) { logRaw(lev, combined); } @@ -119,7 +119,7 @@ class StreamLogOutput : public ICombinedLogOutput { public: StreamLogOutput(std::ostream &stream); - void logRaw(LogLevel lev, const std::string &line); + void logRaw(LogLevel lev, std::string_view line); private: std::ostream &m_stream; @@ -130,7 +130,7 @@ class FileLogOutput : public ICombinedLogOutput { public: void setFile(const std::string &filename, s64 file_size_max); - void logRaw(LogLevel lev, const std::string &line) + void logRaw(LogLevel lev, std::string_view line) { m_stream << line << std::endl; } @@ -154,7 +154,7 @@ public: void updateLogLevel(); - void logRaw(LogLevel lev, const std::string &line); + void logRaw(LogLevel lev, std::string_view line); void clear() { @@ -190,7 +190,7 @@ private: #ifdef __ANDROID__ class AndroidLogOutput : public ICombinedLogOutput { public: - void logRaw(LogLevel lev, const std::string &line); + void logRaw(LogLevel lev, std::string_view line); }; #endif @@ -206,7 +206,7 @@ class LogTarget { public: // Must be thread-safe. These can be called from any thread. virtual bool hasOutput() = 0; - virtual void log(const std::string &buf) = 0; + virtual void log(std::string_view buf) = 0; }; @@ -304,7 +304,7 @@ public: return m_target.hasOutput(); } - void internalFlush(const std::string &buf) { + void internalFlush(std::string_view buf) { m_target.log(buf); } diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index ae83b8df0..e6bfafdcd 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -122,27 +122,30 @@ void script_error(lua_State *L, int pcall_result, const char *mod, const char *f throw LuaError(err_msg); } -static void script_log_add_source(lua_State *L, std::string &message, int stack_depth) +[[nodiscard]] static std::string script_log_add_source(lua_State *L, + std::string_view message, int stack_depth) { + std::string ret(message); if (stack_depth <= 0) - return; + return ret; lua_Debug ar; if (lua_getstack(L, stack_depth, &ar)) { FATAL_ERROR_IF(!lua_getinfo(L, "Sl", &ar), "lua_getinfo() failed"); - message.append(" (at " + std::string(ar.short_src) + ":" + ret.append(" (at ").append(ar.short_src).append(":" + std::to_string(ar.currentline) + ")"); } else { - message.append(" (at ?:?)"); + ret.append(" (at ?:?)"); } + return ret; } -bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to, +bool script_log_unique(lua_State *L, std::string_view message_in, std::ostream &log_to, int stack_depth) { thread_local std::vector logged_messages; - script_log_add_source(L, message, stack_depth); + auto message = script_log_add_source(L, message_in, stack_depth); u64 hash = murmur_hash_64_ua(message.data(), message.length(), 0xBADBABE); if (std::find(logged_messages.begin(), logged_messages.end(), hash) @@ -174,7 +177,7 @@ DeprecatedHandlingMode get_deprecated_handling_mode() return ret; } -void log_deprecated(lua_State *L, std::string message, int stack_depth, bool once) +void log_deprecated(lua_State *L, std::string_view message, int stack_depth, bool once) { DeprecatedHandlingMode mode = get_deprecated_handling_mode(); if (mode == DeprecatedHandlingMode::Ignore) @@ -184,12 +187,12 @@ void log_deprecated(lua_State *L, std::string message, int stack_depth, bool onc if (once) { log = script_log_unique(L, message, warningstream, stack_depth); } else { - script_log_add_source(L, message, stack_depth); - warningstream << message << std::endl; + auto message2 = script_log_add_source(L, message, stack_depth); + warningstream << message2 << std::endl; } if (mode == DeprecatedHandlingMode::Error) - throw LuaError(message); + throw LuaError(std::string(message)); else if (log) infostream << script_get_backtrace(L) << std::endl; } diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index a67b0dad9..a9f9fe226 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -26,6 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include + extern "C" { #include #include @@ -127,7 +129,7 @@ int script_error_handler(lua_State *L); // Takes an error from lua_pcall and throws it as a LuaError void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn); -bool script_log_unique(lua_State *L, std::string message, std::ostream &log_to, +bool script_log_unique(lua_State *L, std::string_view message, std::ostream &log_to, int stack_depth = 1); enum DeprecatedHandlingMode { @@ -152,7 +154,8 @@ DeprecatedHandlingMode get_deprecated_handling_mode(); * (ie: not builtin or core). -1 to disabled. * @param once Log the deprecation warning only once per callsite. */ -void log_deprecated(lua_State *L, std::string message, int stack_depth = 1, bool once = false); +void log_deprecated(lua_State *L, std::string_view message, + int stack_depth = 1, bool once = false); // Safely call string.dump on a function value // (does not pop, leaves one value on stack) diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index fac3e54d1..79eb38629 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -61,13 +61,13 @@ with this program; if not, write to the Free Software Foundation, Inc., int ModApiUtil::l_log(lua_State *L) { NO_MAP_LOCK_REQUIRED; - std::string text; + std::string_view text; LogLevel level = LL_NONE; - if (lua_isnone(L, 2)) { - text = luaL_checkstring(L, 1); + if (lua_isnoneornil(L, 2)) { + text = readParam(L, 1); } else { - std::string name = luaL_checkstring(L, 1); - text = luaL_checkstring(L, 2); + auto name = readParam(L, 1); + text = readParam(L, 2); if (name == "deprecated") { log_deprecated(L, text, 2); return 0; @@ -75,7 +75,7 @@ int ModApiUtil::l_log(lua_State *L) level = Logger::stringToLevel(name); if (level == LL_MAX) { warningstream << "Tried to log at unknown level '" << name - << "'. Defaulting to \"none\"." << std::endl; + << "'. Defaulting to \"none\"." << std::endl; level = LL_NONE; } } diff --git a/src/terminal_chat_console.h b/src/terminal_chat_console.h index 825c76ef4..1bd226609 100644 --- a/src/terminal_chat_console.h +++ b/src/terminal_chat_console.h @@ -32,14 +32,14 @@ struct ChatInterface; class TermLogOutput : public ILogOutput { public: - void logRaw(LogLevel lev, const std::string &line) + void logRaw(LogLevel lev, std::string_view line) { - queue.push_back(std::make_pair(lev, line)); + queue.push_back(std::make_pair(lev, std::string(line))); } virtual void log(LogLevel lev, const std::string &combined, const std::string &time, const std::string &thread_name, - const std::string &payload_text) + std::string_view payload_text) { std::ostringstream os(std::ios_base::binary); os << time << ": [" << thread_name << "] " << payload_text; diff --git a/src/util/stream.h b/src/util/stream.h index 2e61b46d2..620ad74ba 100644 --- a/src/util/stream.h +++ b/src/util/stream.h @@ -20,10 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include -#include +#include #include -template > +template > class StringStreamBuffer : public std::streambuf { public: StringStreamBuffer(Emitter emitter) : m_emitter(emitter) { @@ -38,19 +38,19 @@ public: void push_back(char c) { if (c == '\n' || c == '\r') { if (buffer_index) - m_emitter(std::string(buffer, buffer_index)); + m_emitter(std::string_view(buffer, buffer_index)); buffer_index = 0; } else { buffer[buffer_index++] = c; if (buffer_index >= BufferLength) { - m_emitter(std::string(buffer, buffer_index)); + m_emitter(std::string_view(buffer, buffer_index)); buffer_index = 0; } } } std::streamsize xsputn(const char *s, std::streamsize n) { - for (int i = 0; i < n; ++i) + for (std::streamsize i = 0; i < n; ++i) push_back(s[i]); return n; } From 0c4f03d9a5c082ada54925a062fa224662532eec Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 2 Sep 2024 16:09:32 +0200 Subject: [PATCH 062/200] Reduce include count in headers --- src/client/camera.h | 2 +- src/client/client.cpp | 1 + src/client/client.h | 53 +++++++++++++++---------------- src/client/clientevent.h | 3 +- src/client/clientlauncher.cpp | 1 + src/client/clientlauncher.h | 9 ++++-- src/client/clientmap.h | 12 ++++++- src/client/clientobject.h | 9 +++++- src/client/clouds.h | 10 +++++- src/client/content_cso.cpp | 1 + src/client/content_cso.h | 7 +++- src/client/filecache.cpp | 1 - src/client/gameui.cpp | 3 ++ src/client/gameui.h | 6 ++-- src/client/guiscalingfilter.cpp | 3 ++ src/client/guiscalingfilter.h | 12 ++++++- src/client/hud.h | 13 ++++++++ src/client/imagefilters.h | 9 +++++- src/client/inputhandler.h | 2 +- src/client/sky.h | 12 +++++-- src/client/texturesource.h | 8 ++++- src/client/wieldmesh.h | 15 ++++++++- src/gui/StyleSpec.h | 5 +-- src/gui/guiBackgroundImage.cpp | 1 + src/gui/guiFormSpecMenu.cpp | 1 + src/gui/guiFormSpecMenu.h | 5 ++- src/gui/guiHyperText.cpp | 1 + src/gui/guiItemImage.cpp | 2 ++ src/gui/guiPathSelectMenu.cpp | 1 + src/gui/guiPathSelectMenu.h | 3 +- src/hud.h | 2 +- src/itemdef.h | 2 +- src/mapnode.cpp | 2 +- src/nameidmapping.h | 2 +- src/network/peerhandler.h | 2 -- src/network/serveropcodes.h | 1 - src/player.h | 1 - src/remoteplayer.h | 1 + src/script/lua_api/l_mainmenu.cpp | 1 + 39 files changed, 163 insertions(+), 62 deletions(-) diff --git a/src/client/camera.h b/src/client/camera.h index 88533181b..8f85da0e1 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes.h" #include "inventory.h" #include "util/numeric.h" #include "client/localplayer.h" diff --git a/src/client/client.cpp b/src/client/client.cpp index 3720ec54f..d3ad62512 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -52,6 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "profiler.h" #include "shader.h" #include "gettext.h" +#include "clientdynamicinfo.h" #include "clientmap.h" #include "clientmedia.h" #include "version.h" diff --git a/src/client/client.h b/src/client/client.h index 64ab90eb3..adbe7a70d 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -20,22 +20,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "clientenvironment.h" -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes.h" #include #include #include #include #include #include -#include "clientobject.h" #include "gamedef.h" #include "inventorymanager.h" -#include "client/hud.h" -#include "tileanimation.h" #include "network/address.h" +#include "network/networkprotocol.h" // multiple enums #include "network/peerhandler.h" #include "gameparams.h" -#include "clientdynamicinfo.h" +#include "script/common/c_types.h" // LuaError #include "util/numeric.h" #ifdef SERVER @@ -44,32 +42,33 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f -struct ClientEvent; -struct MeshMakeData; -struct ChatMessage; -class MapBlockMesh; -class RenderingEngine; -class IWritableTextureSource; -class IWritableShaderSource; -class IWritableItemDefManager; -class ISoundManager; -class NodeDefManager; -//class IWritableCraftDefManager; +class Camera; class ClientMediaDownloader; -class SingleMediaDownloader; -struct MapDrawControl; +class ISoundManager; +class IWritableItemDefManager; +class IWritableShaderSource; +class IWritableTextureSource; +class MapBlockMesh; +class MapDatabase; +class MeshUpdateManager; +class Minimap; class ModChannelMgr; class MtEventManager; -struct PointedThing; -struct MapNode; -class MapDatabase; -class Minimap; -struct MinimapMapblock; -class MeshUpdateManager; -class ParticleManager; -class Camera; -struct PlayerControl; class NetworkPacket; +class NodeDefManager; +class ParticleManager; +class RenderingEngine; +class SingleMediaDownloader; +struct ChatMessage; +struct ClientDynamicInfo; +struct ClientEvent; +struct MapDrawControl; +struct MapNode; +struct MeshMakeData; +struct MinimapMapblock; +struct PlayerControl; +struct PointedThing; + namespace con { class IConnection; } diff --git a/src/client/clientevent.h b/src/client/clientevent.h index 243a94596..a53c007dd 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -20,7 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include -#include "irrlichttypes_bloated.h" +#include "irrlichttypes.h" +#include "client/hud.h" // HudElementStat struct ParticleParameters; struct ParticleSpawnerParameters; diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 5826508f6..e86fb4425 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "chat.h" #include "gettext.h" +#include "inputhandler.h" #include "profiler.h" #include "gui/guiEngine.h" #include "fontengine.h" diff --git a/src/client/clientlauncher.h b/src/client/clientlauncher.h index 7b070451a..ad7604c87 100644 --- a/src/client/clientlauncher.h +++ b/src/client/clientlauncher.h @@ -19,11 +19,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" -#include "client/inputhandler.h" -#include "gameparams.h" +#include class RenderingEngine; +class Settings; +class MyEventReceiver; +class InputHandler; +struct GameStartData; +struct MainMenuData; class ClientLauncher { diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 8fac5a471..2f0a2e986 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes_bloated.h" #include "map.h" #include "camera.h" #include @@ -41,6 +41,16 @@ class Client; class ITextureSource; class PartialMeshBuffer; +namespace irr::scene +{ + class IMeshBuffer; +} + +namespace irr::video +{ + class IVideoDriver; +} + /* ClientMap diff --git a/src/client/clientobject.h b/src/client/clientobject.h index f02815e04..8c2c68d15 100644 --- a/src/client/clientobject.h +++ b/src/client/clientobject.h @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes_bloated.h" #include "activeobject.h" #include #include @@ -34,6 +34,13 @@ class LocalPlayer; struct ItemStack; class WieldMeshSceneNode; +namespace irr::scene +{ + class IAnimatedMeshSceneNode; + class ISceneNode; + class ISceneManager; +} + class ClientActiveObject : public ActiveObject { public: diff --git a/src/client/clouds.h b/src/client/clouds.h index e288c5e8a..332aa81e9 100644 --- a/src/client/clouds.h +++ b/src/client/clouds.h @@ -19,14 +19,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "irrlichttypes_bloated.h" #include "constants.h" #include "irr_ptr.h" -#include "irrlichttypes_extrabloated.h" #include "skyparams.h" #include +#include +#include +#include class IShaderSource; +namespace irr::scene +{ + class ISceneManager; +} + // Menu clouds class Clouds; extern Clouds *g_menuclouds; diff --git a/src/client/content_cso.cpp b/src/client/content_cso.cpp index c175df72e..821c2507d 100644 --- a/src/client/content_cso.cpp +++ b/src/client/content_cso.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_cso.h" #include +#include #include "client/texturesource.h" #include "clientenvironment.h" #include "client.h" diff --git a/src/client/content_cso.h b/src/client/content_cso.h index cc9213175..b94580a61 100644 --- a/src/client/content_cso.h +++ b/src/client/content_cso.h @@ -19,8 +19,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes_bloated.h" #include "clientsimpleobject.h" +namespace irr::scene +{ + class ISceneManager; +} + ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr, ClientEnvironment *env, v3f pos, v2f size); diff --git a/src/client/filecache.cpp b/src/client/filecache.cpp index 1f7e605d1..4dd7ec72f 100644 --- a/src/client/filecache.cpp +++ b/src/client/filecache.cpp @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filecache.h" -#include "network/networkprotocol.h" #include "log.h" #include "filesys.h" #include diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 0577943cb..7631f9c78 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -23,10 +23,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "gui/mainmenumanager.h" #include "gui/guiChatConsole.h" +#include "gui/guiFormSpecMenu.h" +#include "util/enriched_string.h" #include "util/pointedthing.h" #include "client.h" #include "clientmap.h" #include "fontengine.h" +#include "hud.h" // HUD_FLAG_* #include "nodedef.h" #include "profiler.h" #include "renderingengine.h" diff --git a/src/client/gameui.h b/src/client/gameui.h index 5b87d43e6..59741c96c 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -22,15 +22,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include -#include "gui/guiFormSpecMenu.h" -#include "util/enriched_string.h" -#include "util/pointedthing.h" #include "game.h" using namespace irr; class Client; +class EnrichedString; class GUIChatConsole; +class GUIFormSpecMenu; struct MapDrawControl; +struct PointedThing; /* * This object intend to contain the core UI elements diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp index b7b9af0ff..eebf44103 100644 --- a/src/client/guiscalingfilter.cpp +++ b/src/client/guiscalingfilter.cpp @@ -23,6 +23,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include #include "client/renderingengine.h" +#include +#include +#include /* Maintain a static cache to store the images that correspond to textures * in a format that's manipulable by code. Some platforms exhibit issues diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h index f2d2fce10..c929bb2d3 100644 --- a/src/client/guiscalingfilter.h +++ b/src/client/guiscalingfilter.h @@ -18,7 +18,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes.h" +#include +#include +#include + +namespace irr::video +{ + class IImage; + class ITexture; + class IVideoDriver; +} /* Manually insert an image into the cache, useful to avoid texture-to-image * conversion whenever we can intercept it. diff --git a/src/client/hud.h b/src/client/hud.h index 07df1c66b..55b24abd3 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#include +#include #include "irr_aabb3d.h" #include "../hud.h" @@ -32,6 +34,17 @@ class InventoryList; class LocalPlayer; struct ItemStack; +namespace irr::scene +{ + class IMesh; +} + +namespace irr::video +{ + class ITexture; + class IVideoDriver; +} + class Hud { public: diff --git a/src/client/imagefilters.h b/src/client/imagefilters.h index e6cbf2d29..f7e06ad09 100644 --- a/src/client/imagefilters.h +++ b/src/client/imagefilters.h @@ -18,7 +18,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes.h" +#include + +namespace irr::video +{ + class IVideoDriver; + class IImage; +} /* Fill in RGB values for transparent pixels, to correct for odd colors * appearing at borders when blending. This is because many PNG optimizers diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 400503a3d..ba85b30ad 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes.h" #include "joystick_controller.h" #include #include "keycode.h" diff --git a/src/client/sky.h b/src/client/sky.h index 2eadea561..eba301e18 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -19,16 +19,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes_bloated.h" #include +#include #include -#include "camera.h" +#include "camera.h" // CameraMode #include "irr_ptr.h" -#include "shader.h" #include "skyparams.h" #define SKY_MATERIAL_COUNT 12 +namespace irr::video +{ + class IVideoDriver; +} + +class IShaderSource; class ITextureSource; // Skybox, rendered with zbuffer turned off, before all other nodes. diff --git a/src/client/texturesource.h b/src/client/texturesource.h index 5fef20821..d4880ed4c 100644 --- a/src/client/texturesource.h +++ b/src/client/texturesource.h @@ -20,10 +20,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes.h" -#include +#include #include #include +namespace irr::video +{ + class IImage; + class ITexture; +} + typedef std::vector Palette; /* diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index 6358a6665..e2c6cd445 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -21,7 +21,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -#include "irrlichttypes_extrabloated.h" +#include "irr_aabb3d.h" +#include "irr_v3d.h" +#include +#include +#include + +namespace irr::scene +{ + class ISceneManager; + class IMesh; + struct SMesh; +} + +using namespace irr; struct ItemStack; class Client; diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index ef7416bca..373590a50 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -17,16 +17,17 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#pragma once + #include "client/texturesource.h" #include "client/fontengine.h" #include "debug.h" -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes_bloated.h" #include "util/string.h" #include #include #include -#pragma once class StyleSpec { diff --git a/src/gui/guiBackgroundImage.cpp b/src/gui/guiBackgroundImage.cpp index 96197ca2c..f3b71bb8c 100644 --- a/src/gui/guiBackgroundImage.cpp +++ b/src/gui/guiBackgroundImage.cpp @@ -19,6 +19,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "client/guiscalingfilter.h" #include "log.h" #include "client/texturesource.h" +#include GUIBackgroundImage::GUIBackgroundImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, const core::rect &rectangle, diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 1fd373b9c..8b572276c 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "client/renderingengine.h" +#include "client/joystick_controller.h" #include "log.h" #include "client/hud.h" // drawItemStack #include "filesys.h" diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 30045a168..12add12e6 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes_bloated.h" #include "irr_ptr.h" #include "inventory.h" #include "inventorymanager.h" @@ -32,8 +32,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiInventoryList.h" #include "guiScrollBar.h" #include "guiTable.h" -#include "network/networkprotocol.h" -#include "client/joystick_controller.h" #include "util/string.h" #include "util/enriched_string.h" #include "StyleSpec.h" @@ -45,6 +43,7 @@ class ISimpleTextureSource; class Client; class GUIScrollContainer; class ISoundManager; +class JoystickController; enum FormspecFieldType { f_Button, diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index 45e5d6e98..6f30ac8ce 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiHyperText.h" #include "guiScrollBar.h" #include "client/fontengine.h" +#include "client/hud.h" // drawItemStack #include "IVideoDriver.h" #include "client/client.h" #include "client/renderingengine.h" diff --git a/src/gui/guiItemImage.cpp b/src/gui/guiItemImage.cpp index a02285fcb..0c543e391 100644 --- a/src/gui/guiItemImage.cpp +++ b/src/gui/guiItemImage.cpp @@ -19,7 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiItemImage.h" #include "client/client.h" +#include "client/hud.h" // drawItemStack #include "inventory.h" +#include GUIItemImage::GUIItemImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, const core::rect &rectangle, const std::string &item_name, diff --git a/src/gui/guiPathSelectMenu.cpp b/src/gui/guiPathSelectMenu.cpp index 9c63e06b5..b4c3d36c3 100644 --- a/src/gui/guiPathSelectMenu.cpp +++ b/src/gui/guiPathSelectMenu.cpp @@ -18,6 +18,7 @@ */ #include "guiPathSelectMenu.h" +#include "guiFormSpecMenu.h" //required because of TextDest only !!! GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, diff --git a/src/gui/guiPathSelectMenu.h b/src/gui/guiPathSelectMenu.h index 11307d682..7757b2d7b 100644 --- a/src/gui/guiPathSelectMenu.h +++ b/src/gui/guiPathSelectMenu.h @@ -23,7 +23,8 @@ #include "modalMenu.h" #include "IGUIFileOpenDialog.h" -#include "guiFormSpecMenu.h" //required because of TextDest only !!! + +struct TextDest; class GUIFileSelectMenu : public GUIModalMenu { diff --git a/src/hud.h b/src/hud.h index d32ab784e..ac79aa750 100644 --- a/src/hud.h +++ b/src/hud.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes_bloated.h" #include #include "common/c_types.h" diff --git a/src/itemdef.h b/src/itemdef.h index 782dad816..4a227ebe1 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes_bloated.h" #include #include #include diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 6fe169e90..4e3c60192 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -17,7 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "irrlichttypes_extrabloated.h" +#include "irrlichttypes_bloated.h" #include "mapnode.h" #include "porting.h" #include "nodedef.h" diff --git a/src/nameidmapping.h b/src/nameidmapping.h index 47f424cbc..b200e3e7c 100644 --- a/src/nameidmapping.h +++ b/src/nameidmapping.h @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include -#include "irrlichttypes_bloated.h" +#include "irrlichttypes.h" typedef std::unordered_map IdToNameMap; typedef std::unordered_map NameToIdMap; diff --git a/src/network/peerhandler.h b/src/network/peerhandler.h index 64e2c85fb..adda995b3 100644 --- a/src/network/peerhandler.h +++ b/src/network/peerhandler.h @@ -19,8 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "networkprotocol.h" - namespace con { diff --git a/src/network/serveropcodes.h b/src/network/serveropcodes.h index 275270ab9..509d2b4b2 100644 --- a/src/network/serveropcodes.h +++ b/src/network/serveropcodes.h @@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "server.h" -#include "networkprotocol.h" class NetworkPacket; // Note: don't forward-declare Server here (#14324) diff --git a/src/player.h b/src/player.h index dd2be4986..7d92808cf 100644 --- a/src/player.h +++ b/src/player.h @@ -22,7 +22,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "inventory.h" #include "constants.h" -#include "network/networkprotocol.h" #include "util/basic_macros.h" #include "util/string.h" #include diff --git a/src/remoteplayer.h b/src/remoteplayer.h index e0c7ab744..4923c307d 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "skyparams.h" #include "lighting.h" +#include "network/networkprotocol.h" // session_t class PlayerSAO; diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 78808792b..bf20f14ba 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content/subgames.h" #include "mapgen/mapgen.h" #include "settings.h" +#include "clientdynamicinfo.h" #include "client/client.h" #include "client/renderingengine.h" #include "network/networkprotocol.h" From f23d7459b3bc92a2f28295a5674e20df8e7e449d Mon Sep 17 00:00:00 2001 From: DS Date: Mon, 2 Sep 2024 16:09:42 +0200 Subject: [PATCH 063/200] Allow to disable transparency sorting entirely (#15101) --- builtin/settingtypes.txt | 5 +++-- src/client/clientmap.cpp | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index dd193a0c9..d03634506 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1854,8 +1854,9 @@ shader_path (Shader path) path # OpenGL is the default for desktop, and OGLES2 for Android. video_driver (Video driver) enum ,opengl,opengl3,ogles2 -# Distance in nodes at which transparency depth sorting is enabled -# Use this to limit the performance impact of transparency depth sorting +# Distance in nodes at which transparency depth sorting is enabled. +# Use this to limit the performance impact of transparency depth sorting. +# Set to 0 to disable it entirely. transparency_sorting_distance (Transparency Sorting Distance) int 16 0 128 # Radius of cloud area stated in number of 64 node cloud squares. diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 7d40dec92..5e4023569 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -1311,9 +1311,9 @@ void ClientMap::updateTransparentMeshBuffers() ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG); u32 sorted_blocks = 0; u32 unsorted_blocks = 0; + bool transparency_sorting_enabled = m_cache_transparency_sorting_distance > 0; f32 sorting_distance = m_cache_transparency_sorting_distance * BS; - // Update the order of transparent mesh buffers in each mesh for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) { MapBlock *block = it->second; @@ -1323,13 +1323,19 @@ void ClientMap::updateTransparentMeshBuffers() if (m_needs_update_transparent_meshes || blockmesh->getTransparentBuffers().size() == 0) { + bool do_sort_block = transparency_sorting_enabled; - v3f mesh_sphere_center = intToFloat(block->getPosRelative(), BS) - + blockmesh->getBoundingSphereCenter(); - f32 mesh_sphere_radius = blockmesh->getBoundingRadius(); - f32 distance_sq = m_camera_position.getDistanceFromSQ(mesh_sphere_center); + if (do_sort_block) { + v3f mesh_sphere_center = intToFloat(block->getPosRelative(), BS) + + blockmesh->getBoundingSphereCenter(); + f32 mesh_sphere_radius = blockmesh->getBoundingRadius(); + f32 distance_sq = m_camera_position.getDistanceFromSQ(mesh_sphere_center); - if (distance_sq <= std::pow(sorting_distance + mesh_sphere_radius, 2.0f)) { + if (distance_sq > std::pow(sorting_distance + mesh_sphere_radius, 2.0f)) + do_sort_block = false; + } + + if (do_sort_block) { blockmesh->updateTransparentBuffers(m_camera_position, block->getPos()); ++sorted_blocks; } else { From 6105804f0020a77f2e3f6ddcf6d300e76abc01b5 Mon Sep 17 00:00:00 2001 From: 1F616EMO~nya Date: Mon, 2 Sep 2024 22:10:01 +0800 Subject: [PATCH 064/200] Show full texture string in "generateImage(): Failed to generate" errors (#15033) --- src/client/imagesource.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/imagesource.cpp b/src/client/imagesource.cpp index adc39f130..bb6668264 100644 --- a/src/client/imagesource.cpp +++ b/src/client/imagesource.cpp @@ -1908,7 +1908,8 @@ video::IImage* ImageSource::generateImage(std::string_view name, video::IImage *tmp = generateImage(name2, source_image_names); if (!tmp) { errorstream << "generateImage(): " - "Failed to generate \"" << name2 << "\"" + "Failed to generate \"" << name2 << "\"\n" + "part of texture \"" << name << "\"" << std::endl; return NULL; } @@ -1923,7 +1924,8 @@ video::IImage* ImageSource::generateImage(std::string_view name, } else if (!generateImagePart(last_part_of_name, baseimg, source_image_names)) { // Generate image according to part of name errorstream << "generateImage(): " - "Failed to generate \"" << last_part_of_name << "\"" + "Failed to generate \"" << last_part_of_name << "\"\n" + "part of texture \"" << name << "\"" << std::endl; } From 538b8b9b3457bb492b2b10a4acf869ad0ca98c26 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 29 Aug 2024 16:01:29 +0200 Subject: [PATCH 065/200] Avoid unsafety with stack-allocated mesh buffer --- src/client/hud.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 007421f7a..1521e62f8 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -154,7 +154,7 @@ Hud::Hud(Client *client, LocalPlayer *player, b.getMaterial().Lighting = false; b.getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - b.setHardwareMappingHint(scene::EHM_STATIC); + //b.setHardwareMappingHint(scene::EHM_STATIC); // FIXME: incorrectly stack allocated, not safe! } void Hud::readScalingSetting() From 5d6e15bc49db8b0cd40de9fb4a1a7534071e40f0 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 Aug 2024 16:50:41 +0200 Subject: [PATCH 066/200] Split CVertexBuffer from CMeshBuffer --- irr/include/CMeshBuffer.h | 62 +++++++++------- irr/include/CVertexBuffer.h | 122 ++++++++++++++++++++++++++++++++ irr/include/IVertexBuffer.h | 51 +++++++++---- irr/src/CBillboardSceneNode.cpp | 39 +++++----- irr/src/CMeshManipulator.cpp | 6 +- irr/src/COBJMeshFileLoader.cpp | 4 +- src/client/clouds.cpp | 7 +- src/client/hud.cpp | 11 +-- src/client/minimap.cpp | 11 +-- src/client/sky.cpp | 11 +-- 10 files changed, 246 insertions(+), 78 deletions(-) create mode 100644 irr/include/CVertexBuffer.h diff --git a/irr/include/CMeshBuffer.h b/irr/include/CMeshBuffer.h index 0b0c3bb92..a81d668bd 100644 --- a/irr/include/CMeshBuffer.h +++ b/irr/include/CMeshBuffer.h @@ -6,6 +6,7 @@ #include #include "IMeshBuffer.h" +#include "CVertexBuffer.h" namespace irr { @@ -18,11 +19,17 @@ class CMeshBuffer : public IMeshBuffer public: //! Default constructor for empty meshbuffer CMeshBuffer() : - ChangedID_Vertex(1), ChangedID_Index(1), MappingHint_Vertex(EHM_NEVER), MappingHint_Index(EHM_NEVER), HWBuffer(NULL), PrimitiveType(EPT_TRIANGLES) + ChangedID_Index(1), MappingHint_Index(EHM_NEVER), HWBuffer(NULL), PrimitiveType(EPT_TRIANGLES) { #ifdef _DEBUG setDebugName("CMeshBuffer"); #endif + Vertices = new CVertexBuffer(); + } + + ~CMeshBuffer() + { + Vertices->drop(); } //! Get material of this meshbuffer @@ -43,21 +50,27 @@ public: /** \return Pointer to vertices. */ const void *getVertices() const override { - return Vertices.data(); + return Vertices->getData(); } //! Get pointer to vertices /** \return Pointer to vertices. */ void *getVertices() override { - return Vertices.data(); + return Vertices->getData(); } //! Get number of vertices /** \return Number of vertices. */ u32 getVertexCount() const override { - return static_cast(Vertices.size()); + return Vertices->getCount(); + } + + // TEMPORARY helper for direct buffer acess + inline auto &VertexBuffer() + { + return Vertices->Data; } //! Get type of index data which is stored in this meshbuffer. @@ -107,11 +120,11 @@ public: /** should be called if the mesh changed. */ void recalculateBoundingBox() override { - if (!Vertices.empty()) { - BoundingBox.reset(Vertices[0].Pos); - const irr::u32 vsize = Vertices.size(); + if (Vertices->getCount()) { + BoundingBox.reset(Vertices->getPosition(0)); + const irr::u32 vsize = Vertices->getCount(); for (u32 i = 1; i < vsize; ++i) - BoundingBox.addInternalPoint(Vertices[i].Pos); + BoundingBox.addInternalPoint(Vertices->getPosition(i)); } else BoundingBox.reset(0, 0, 0); } @@ -120,43 +133,43 @@ public: /** \return Type of vertex data. */ video::E_VERTEX_TYPE getVertexType() const override { - return T::getType(); + return Vertices->getType(); } //! returns position of vertex i const core::vector3df &getPosition(u32 i) const override { - return Vertices[i].Pos; + return Vertices->getPosition(i); } //! returns position of vertex i core::vector3df &getPosition(u32 i) override { - return Vertices[i].Pos; + return Vertices->getPosition(i); } //! returns normal of vertex i const core::vector3df &getNormal(u32 i) const override { - return Vertices[i].Normal; + return Vertices->getNormal(i); } //! returns normal of vertex i core::vector3df &getNormal(u32 i) override { - return Vertices[i].Normal; + return Vertices->getNormal(i); } //! returns texture coord of vertex i const core::vector2df &getTCoords(u32 i) const override { - return Vertices[i].TCoords; + return Vertices->getTCoords(i); } //! returns texture coord of vertex i core::vector2df &getTCoords(u32 i) override { - return Vertices[i].TCoords; + return Vertices->getTCoords(i); } //! Append the vertices and indices to the current buffer @@ -169,9 +182,9 @@ public: const u32 indexCount = getIndexCount(); auto *vt = static_cast(vertices); - Vertices.insert(Vertices.end(), vt, vt + numVertices); + Vertices->Data.insert(Vertices->Data.end(), vt, vt + numVertices); for (u32 i = vertexCount; i < getVertexCount(); i++) - BoundingBox.addInternalPoint(Vertices[i].Pos); + BoundingBox.addInternalPoint(Vertices->getPosition(i)); Indices.insert(Indices.end(), indices, indices + numIndices); if (vertexCount != 0) { @@ -183,7 +196,7 @@ public: //! get the current hardware mapping hint E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const override { - return MappingHint_Vertex; + return Vertices->getHardwareMappingHint(); } //! get the current hardware mapping hint @@ -196,7 +209,7 @@ public: void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint, E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override { if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX) - MappingHint_Vertex = NewMappingHint; + Vertices->setHardwareMappingHint(NewMappingHint); if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX) MappingHint_Index = NewMappingHint; } @@ -217,14 +230,14 @@ public: void setDirty(E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override { if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX) - ++ChangedID_Vertex; + Vertices->setDirty(); if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX) ++ChangedID_Index; } //! Get the currently used ID for identification of changes. /** This shouldn't be used for anything outside the VideoDriver. */ - u32 getChangedID_Vertex() const override { return ChangedID_Vertex; } + u32 getChangedID_Vertex() const override { return Vertices->getChangedID(); } //! Get the currently used ID for identification of changes. /** This shouldn't be used for anything outside the VideoDriver. */ @@ -240,18 +253,15 @@ public: return HWBuffer; } - u32 ChangedID_Vertex; u32 ChangedID_Index; - //! hardware mapping hint - E_HARDWARE_MAPPING MappingHint_Vertex; E_HARDWARE_MAPPING MappingHint_Index; mutable void *HWBuffer; //! Material for this meshbuffer. video::SMaterial Material; - //! Vertices of this buffer - std::vector Vertices; + //! Vertex buffer + CVertexBuffer *Vertices; //! Indices into the vertices of this buffer. std::vector Indices; //! Bounding box of this meshbuffer. diff --git a/irr/include/CVertexBuffer.h b/irr/include/CVertexBuffer.h new file mode 100644 index 000000000..6861913bc --- /dev/null +++ b/irr/include/CVertexBuffer.h @@ -0,0 +1,122 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#pragma once + +#include +#include "IVertexBuffer.h" + +namespace irr +{ +namespace scene +{ +//! Template implementation of the IVertexBuffer interface +template +class CVertexBuffer : public IVertexBuffer +{ +public: + //! Default constructor for empty buffer + CVertexBuffer() + { +#ifdef _DEBUG + setDebugName("CVertexBuffer"); +#endif + } + + const void *getData() const override + { + return Data.data(); + } + + void *getData() override + { + return Data.data(); + } + + u32 getCount() const override + { + return static_cast(Data.size()); + } + + video::E_VERTEX_TYPE getType() const override + { + return T::getType(); + } + + const core::vector3df &getPosition(u32 i) const override + { + return Data[i].Pos; + } + + core::vector3df &getPosition(u32 i) override + { + return Data[i].Pos; + } + + const core::vector3df &getNormal(u32 i) const override + { + return Data[i].Normal; + } + + core::vector3df &getNormal(u32 i) override + { + return Data[i].Normal; + } + + const core::vector2df &getTCoords(u32 i) const override + { + return Data[i].TCoords; + } + + core::vector2df &getTCoords(u32 i) override + { + return Data[i].TCoords; + } + + E_HARDWARE_MAPPING getHardwareMappingHint() const override + { + return MappingHint; + } + + void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint) override + { + MappingHint = NewMappingHint; + } + + void setDirty() override + { + ++ChangedID; + } + + u32 getChangedID() const override { return ChangedID; } + + void setHWBuffer(void *ptr) const override + { + HWBuffer = ptr; + } + + void *getHWBuffer() const override + { + return HWBuffer; + } + + u32 ChangedID = 1; + + //! hardware mapping hint + E_HARDWARE_MAPPING MappingHint = EHM_NEVER; + mutable void *HWBuffer = nullptr; + + //! Vertices of this buffer + std::vector Data; +}; + +//! Standard buffer +typedef CVertexBuffer SVertexBuffer; +//! Buffer with two texture coords per vertex, e.g. for lightmaps +typedef CVertexBuffer SVertexBufferLightMap; +//! Buffer with vertices having tangents stored, e.g. for normal mapping +typedef CVertexBuffer SVertexBufferTangents; + +} // end namespace scene +} // end namespace irr diff --git a/irr/include/IVertexBuffer.h b/irr/include/IVertexBuffer.h index a4ec5efd9..e5be83904 100644 --- a/irr/include/IVertexBuffer.h +++ b/irr/include/IVertexBuffer.h @@ -17,24 +17,47 @@ namespace scene class IVertexBuffer : public virtual IReferenceCounted { public: - virtual void *getData() = 0; + //! Get type of vertex data which is stored in this meshbuffer. + /** \return Vertex type of this buffer. */ virtual video::E_VERTEX_TYPE getType() const = 0; - virtual void setType(video::E_VERTEX_TYPE vertexType) = 0; - virtual u32 stride() const = 0; - virtual u32 size() const = 0; - virtual void push_back(const video::S3DVertex &element) = 0; - virtual video::S3DVertex &operator[](const u32 index) const = 0; - virtual video::S3DVertex &getLast() = 0; - virtual void set_used(u32 usedNow) = 0; - virtual void reallocate(u32 new_size) = 0; - virtual u32 allocated_size() const = 0; - virtual video::S3DVertex *pointer() = 0; + + //! Get access to vertex data. The data is an array of vertices. + /** Which vertex type is used can be determined by getVertexType(). + \return Pointer to array of vertices. */ + virtual const void *getData() const = 0; + + //! Get access to vertex data. The data is an array of vertices. + /** Which vertex type is used can be determined by getVertexType(). + \return Pointer to array of vertices. */ + virtual void *getData() = 0; + + //! Get amount of vertices in meshbuffer. + /** \return Number of vertices in this buffer. */ + virtual u32 getCount() const = 0; + + //! returns position of vertex i + virtual const core::vector3df &getPosition(u32 i) const = 0; + + //! returns position of vertex i + virtual core::vector3df &getPosition(u32 i) = 0; + + //! returns normal of vertex i + virtual const core::vector3df &getNormal(u32 i) const = 0; + + //! returns normal of vertex i + virtual core::vector3df &getNormal(u32 i) = 0; + + //! returns texture coord of vertex i + virtual const core::vector2df &getTCoords(u32 i) const = 0; + + //! returns texture coord of vertex i + virtual core::vector2df &getTCoords(u32 i) = 0; //! get the current hardware mapping hint virtual E_HARDWARE_MAPPING getHardwareMappingHint() const = 0; //! set the hardware mapping hint, for driver - virtual void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint) = 0; + virtual void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint) = 0; //! flags the meshbuffer as changed, reloads hardware buffers virtual void setDirty() = 0; @@ -42,6 +65,10 @@ public: //! Get the currently used ID for identification of changes. /** This shouldn't be used for anything outside the VideoDriver. */ virtual u32 getChangedID() const = 0; + + //! Used by the VideoDriver to remember the buffer link. + virtual void setHWBuffer(void *ptr) const = 0; + virtual void *getHWBuffer() const = 0; }; } // end namespace scene diff --git a/irr/src/CBillboardSceneNode.cpp b/irr/src/CBillboardSceneNode.cpp index 7be139f96..cf9a58818 100644 --- a/irr/src/CBillboardSceneNode.cpp +++ b/irr/src/CBillboardSceneNode.cpp @@ -26,7 +26,9 @@ CBillboardSceneNode::CBillboardSceneNode(ISceneNode *parent, ISceneManager *mgr, setSize(size); - Buffer->Vertices.resize(4); + auto &Vertices = Buffer->Vertices->Data; + + Vertices.resize(4); Buffer->Indices.resize(6); Buffer->Indices[0] = 0; @@ -36,17 +38,17 @@ CBillboardSceneNode::CBillboardSceneNode(ISceneNode *parent, ISceneManager *mgr, Buffer->Indices[4] = 3; Buffer->Indices[5] = 2; - Buffer->Vertices[0].TCoords.set(1.0f, 1.0f); - Buffer->Vertices[0].Color = colorBottom; + Vertices[0].TCoords.set(1.0f, 1.0f); + Vertices[0].Color = colorBottom; - Buffer->Vertices[1].TCoords.set(1.0f, 0.0f); - Buffer->Vertices[1].Color = colorTop; + Vertices[1].TCoords.set(1.0f, 0.0f); + Vertices[1].Color = colorTop; - Buffer->Vertices[2].TCoords.set(0.0f, 0.0f); - Buffer->Vertices[2].Color = colorTop; + Vertices[2].TCoords.set(0.0f, 0.0f); + Vertices[2].Color = colorTop; - Buffer->Vertices[3].TCoords.set(0.0f, 1.0f); - Buffer->Vertices[3].Color = colorBottom; + Vertices[3].TCoords.set(0.0f, 1.0f); + Vertices[3].Color = colorBottom; } CBillboardSceneNode::~CBillboardSceneNode() @@ -114,7 +116,7 @@ void CBillboardSceneNode::updateMesh(const irr::scene::ICameraSceneNode *camera) view *= -1.0f; - auto *vertices = Buffer->Vertices.data(); + auto &vertices = Buffer->Vertices->Data; for (s32 i = 0; i < 4; ++i) vertices[i].Normal = view; @@ -211,8 +213,9 @@ void CBillboardSceneNode::getSize(f32 &height, f32 &bottomEdgeWidth, //! \param overallColor: the color to set void CBillboardSceneNode::setColor(const video::SColor &overallColor) { + auto &vertices = Buffer->Vertices->Data; for (u32 vertex = 0; vertex < 4; ++vertex) - Buffer->Vertices[vertex].Color = overallColor; + vertices[vertex].Color = overallColor; } //! Set the color of the top and bottom vertices of the billboard @@ -221,10 +224,11 @@ void CBillboardSceneNode::setColor(const video::SColor &overallColor) void CBillboardSceneNode::setColor(const video::SColor &topColor, const video::SColor &bottomColor) { - Buffer->Vertices[0].Color = bottomColor; - Buffer->Vertices[1].Color = topColor; - Buffer->Vertices[2].Color = topColor; - Buffer->Vertices[3].Color = bottomColor; + auto &vertices = Buffer->Vertices->Data; + vertices[0].Color = bottomColor; + vertices[1].Color = topColor; + vertices[2].Color = topColor; + vertices[3].Color = bottomColor; } //! Gets the color of the top and bottom vertices of the billboard @@ -233,8 +237,9 @@ void CBillboardSceneNode::setColor(const video::SColor &topColor, void CBillboardSceneNode::getColor(video::SColor &topColor, video::SColor &bottomColor) const { - bottomColor = Buffer->Vertices[0].Color; - topColor = Buffer->Vertices[1].Color; + auto &vertices = Buffer->Vertices->Data; + bottomColor = vertices[0].Color; + topColor = vertices[1].Color; } //! Creates a clone of this scene node and its children. diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index db6c39812..5abaffec8 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -133,7 +133,7 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const SMeshBuffer *buffer = new SMeshBuffer(); buffer->Material = mb->getMaterial(); auto *vt = static_cast(mb->getVertices()); - buffer->Vertices.insert(buffer->Vertices.end(), vt, vt + mb->getVertexCount()); + buffer->VertexBuffer().insert(buffer->VertexBuffer().end(), vt, vt + mb->getVertexCount()); auto *indices = mb->getIndices(); buffer->Indices.insert(buffer->Indices.end(), indices, indices + mb->getIndexCount()); clone->addMeshBuffer(buffer); @@ -143,7 +143,7 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const SMeshBufferLightMap *buffer = new SMeshBufferLightMap(); buffer->Material = mb->getMaterial(); auto *vt = static_cast(mb->getVertices()); - buffer->Vertices.insert(buffer->Vertices.end(), vt, vt + mb->getVertexCount()); + buffer->VertexBuffer().insert(buffer->VertexBuffer().end(), vt, vt + mb->getVertexCount()); auto *indices = mb->getIndices(); buffer->Indices.insert(buffer->Indices.end(), indices, indices + mb->getIndexCount()); clone->addMeshBuffer(buffer); @@ -153,7 +153,7 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const SMeshBufferTangents *buffer = new SMeshBufferTangents(); buffer->Material = mb->getMaterial(); auto *vt = static_cast(mb->getVertices()); - buffer->Vertices.insert(buffer->Vertices.end(), vt, vt + mb->getVertexCount()); + buffer->VertexBuffer().insert(buffer->VertexBuffer().end(), vt, vt + mb->getVertexCount()); auto *indices = mb->getIndices(); buffer->Indices.insert(buffer->Indices.end(), indices, indices + mb->getIndexCount()); clone->addMeshBuffer(buffer); diff --git a/irr/src/COBJMeshFileLoader.cpp b/irr/src/COBJMeshFileLoader.cpp index 4c5a5328d..ca382c418 100644 --- a/irr/src/COBJMeshFileLoader.cpp +++ b/irr/src/COBJMeshFileLoader.cpp @@ -228,8 +228,8 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file) if (n != currMtl->VertMap.end()) { vertLocation = n->second; } else { - currMtl->Meshbuffer->Vertices.push_back(v); - vertLocation = currMtl->Meshbuffer->Vertices.size() - 1; + currMtl->Meshbuffer->VertexBuffer().push_back(v); + vertLocation = currMtl->Meshbuffer->VertexBuffer().size() - 1; currMtl->VertMap.emplace(v, vertLocation); } diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index ebb8b9000..4b91b04d3 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -178,13 +178,14 @@ void Clouds::updateMesh() auto *mb = m_meshbuffer.get(); + auto &vertices = mb->Vertices->Data; { const u32 vertex_count = num_faces_to_draw * 16 * m_cloud_radius_i * m_cloud_radius_i; const u32 quad_count = vertex_count / 4; const u32 index_count = quad_count * 6; // reserve memory - mb->Vertices.reserve(vertex_count); + vertices.reserve(vertex_count); mb->Indices.reserve(index_count); } @@ -192,7 +193,7 @@ void Clouds::updateMesh() #define INAREA(x, z, radius) \ ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius)) - mb->Vertices.clear(); + vertices.clear(); for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++) for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++) { @@ -312,7 +313,7 @@ void Clouds::updateMesh() for (video::S3DVertex &vertex : v) { vertex.Pos += pos; - mb->Vertices.push_back(vertex); + vertices.push_back(vertex); } } } diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 1521e62f8..128c3f9d9 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -134,16 +134,17 @@ Hud::Hud(Client *client, LocalPlayer *player, // Prepare mesh for compass drawing auto &b = m_rotation_mesh_buffer; - b.Vertices.resize(4); + auto &vertices = b.Vertices->Data; + vertices.resize(4); b.Indices.resize(6); video::SColor white(255, 255, 255, 255); v3f normal(0.f, 0.f, 1.f); - b.Vertices[0] = video::S3DVertex(v3f(-1.f, -1.f, 0.f), normal, white, v2f(0.f, 1.f)); - b.Vertices[1] = video::S3DVertex(v3f(-1.f, 1.f, 0.f), normal, white, v2f(0.f, 0.f)); - b.Vertices[2] = video::S3DVertex(v3f( 1.f, 1.f, 0.f), normal, white, v2f(1.f, 0.f)); - b.Vertices[3] = video::S3DVertex(v3f( 1.f, -1.f, 0.f), normal, white, v2f(1.f, 1.f)); + vertices[0] = video::S3DVertex(v3f(-1.f, -1.f, 0.f), normal, white, v2f(0.f, 1.f)); + vertices[1] = video::S3DVertex(v3f(-1.f, 1.f, 0.f), normal, white, v2f(0.f, 0.f)); + vertices[2] = video::S3DVertex(v3f( 1.f, 1.f, 0.f), normal, white, v2f(1.f, 0.f)); + vertices[3] = video::S3DVertex(v3f( 1.f, -1.f, 0.f), normal, white, v2f(1.f, 1.f)); b.Indices[0] = 0; b.Indices[1] = 1; diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 9f1f04359..2368057fc 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -555,14 +555,15 @@ v3f Minimap::getYawVec() scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() { scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - buf->Vertices.resize(4); + auto &vertices = buf->Vertices->Data; + vertices.resize(4); buf->Indices.resize(6); static const video::SColor c(255, 255, 255, 255); - buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); - buf->Vertices[1] = video::S3DVertex(-1, 1, 0, 0, 0, 1, c, 0, 0); - buf->Vertices[2] = video::S3DVertex( 1, 1, 0, 0, 0, 1, c, 1, 0); - buf->Vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1); + vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); + vertices[1] = video::S3DVertex(-1, 1, 0, 0, 0, 1, c, 0, 0); + vertices[2] = video::S3DVertex( 1, 1, 0, 0, 0, 1, c, 1, 0); + vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1); buf->Indices[0] = 0; buf->Indices[1] = 1; diff --git a/src/client/sky.cpp b/src/client/sky.cpp index c68d13984..19b9765a5 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -830,7 +830,8 @@ void Sky::updateStars() warningstream << "Requested " << m_star_params.count << " stars but " << 0x4000 << " is the max\n"; m_star_params.count = 0x4000; } - m_stars->Vertices.reserve(4 * m_star_params.count); + auto &vertices = m_stars->Vertices->Data; + vertices.reserve(4 * m_star_params.count); m_stars->Indices.reserve(6 * m_star_params.count); video::SColor fallback_color = m_star_params.starcolor; // used on GLES 2 “without shaders” @@ -852,10 +853,10 @@ void Sky::updateStars() a.rotateVect(p1); a.rotateVect(p2); a.rotateVect(p3); - m_stars->Vertices.push_back(video::S3DVertex(p, {}, fallback_color, {})); - m_stars->Vertices.push_back(video::S3DVertex(p1, {}, fallback_color, {})); - m_stars->Vertices.push_back(video::S3DVertex(p2, {}, fallback_color, {})); - m_stars->Vertices.push_back(video::S3DVertex(p3, {}, fallback_color, {})); + vertices.push_back(video::S3DVertex(p, {}, fallback_color, {})); + vertices.push_back(video::S3DVertex(p1, {}, fallback_color, {})); + vertices.push_back(video::S3DVertex(p2, {}, fallback_color, {})); + vertices.push_back(video::S3DVertex(p3, {}, fallback_color, {})); } for (u16 i = 0; i < m_star_params.count; i++) { m_stars->Indices.push_back(i * 4 + 0); From 47e4c33a505baec177d3a6a3e8e3d234b549c08d Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 Aug 2024 17:40:29 +0200 Subject: [PATCH 067/200] Split CIndexBuffer from CMeshBuffer --- irr/include/CIndexBuffer.h | 89 +++++++++++++++++++++++++++++++++ irr/include/CMeshBuffer.h | 38 ++++++++------ irr/include/IIndexBuffer.h | 39 +++++++-------- irr/src/CBillboardSceneNode.cpp | 15 +++--- irr/src/CMeshManipulator.cpp | 6 +-- irr/src/COBJMeshFileLoader.cpp | 7 +-- src/client/clientmap.cpp | 7 +-- src/client/clouds.cpp | 17 ++++--- src/client/hud.cpp | 15 +++--- src/client/mapblock_mesh.cpp | 18 +++---- src/client/mapblock_mesh.h | 20 ++++---- src/client/minimap.cpp | 15 +++--- src/client/sky.cpp | 15 +++--- 13 files changed, 197 insertions(+), 104 deletions(-) create mode 100644 irr/include/CIndexBuffer.h diff --git a/irr/include/CIndexBuffer.h b/irr/include/CIndexBuffer.h new file mode 100644 index 000000000..4701c2e34 --- /dev/null +++ b/irr/include/CIndexBuffer.h @@ -0,0 +1,89 @@ +// Copyright (C) 2002-2012 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#pragma once + +#include +#include "IIndexBuffer.h" + +namespace irr +{ +namespace scene +{ +//! Template implementation of the IIndexBuffer interface +template +class CIndexBuffer : public IIndexBuffer +{ +public: + //! Default constructor for empty buffer + CIndexBuffer() + { +#ifdef _DEBUG + setDebugName("CIndexBuffer"); +#endif + } + + video::E_INDEX_TYPE getType() const + { + static_assert(sizeof(T) == 2 || sizeof(T) == 4, "invalid index type"); + return sizeof(T) == 2 ? video::EIT_16BIT : video::EIT_32BIT; + } + + const void *getData() const override + { + return Data.data(); + } + + void *getData() override + { + return Data.data(); + } + + u32 getCount() const override + { + return static_cast(Data.size()); + } + + E_HARDWARE_MAPPING getHardwareMappingHint() const override + { + return MappingHint; + } + + void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint) override + { + MappingHint = NewMappingHint; + } + + void setDirty() override + { + ++ChangedID; + } + + u32 getChangedID() const override { return ChangedID; } + + void setHWBuffer(void *ptr) const override + { + HWBuffer = ptr; + } + + void *getHWBuffer() const override + { + return HWBuffer; + } + + u32 ChangedID = 1; + + //! hardware mapping hint + E_HARDWARE_MAPPING MappingHint = EHM_NEVER; + mutable void *HWBuffer = nullptr; + + //! Indices of this buffer + std::vector Data; +}; + +//! Standard 16-bit buffer +typedef CIndexBuffer SIndexBuffer; + +} // end namespace scene +} // end namespace irr diff --git a/irr/include/CMeshBuffer.h b/irr/include/CMeshBuffer.h index a81d668bd..0b47494cd 100644 --- a/irr/include/CMeshBuffer.h +++ b/irr/include/CMeshBuffer.h @@ -7,6 +7,7 @@ #include #include "IMeshBuffer.h" #include "CVertexBuffer.h" +#include "CIndexBuffer.h" namespace irr { @@ -19,17 +20,19 @@ class CMeshBuffer : public IMeshBuffer public: //! Default constructor for empty meshbuffer CMeshBuffer() : - ChangedID_Index(1), MappingHint_Index(EHM_NEVER), HWBuffer(NULL), PrimitiveType(EPT_TRIANGLES) + HWBuffer(NULL), PrimitiveType(EPT_TRIANGLES) { #ifdef _DEBUG setDebugName("CMeshBuffer"); #endif Vertices = new CVertexBuffer(); + Indices = new SIndexBuffer(); } ~CMeshBuffer() { Vertices->drop(); + Indices->drop(); } //! Get material of this meshbuffer @@ -77,28 +80,34 @@ public: /** \return Index type of this buffer. */ video::E_INDEX_TYPE getIndexType() const override { - return video::EIT_16BIT; + return Indices->getType(); } //! Get pointer to indices /** \return Pointer to indices. */ const u16 *getIndices() const override { - return Indices.data(); + return static_cast(Indices->getData()); } //! Get pointer to indices /** \return Pointer to indices. */ u16 *getIndices() override { - return Indices.data(); + return static_cast(Indices->getData()); } //! Get number of indices /** \return Number of indices. */ u32 getIndexCount() const override { - return static_cast(Indices.size()); + return Indices->getCount(); + } + + // TEMPORARY helper for direct buffer acess + inline auto &IndexBuffer() + { + return Indices->Data; } //! Get the axis aligned bounding box @@ -186,10 +195,10 @@ public: for (u32 i = vertexCount; i < getVertexCount(); i++) BoundingBox.addInternalPoint(Vertices->getPosition(i)); - Indices.insert(Indices.end(), indices, indices + numIndices); + Indices->Data.insert(Indices->Data.end(), indices, indices + numIndices); if (vertexCount != 0) { for (u32 i = indexCount; i < getIndexCount(); i++) - Indices[i] += vertexCount; + Indices->Data[i] += vertexCount; } } @@ -202,7 +211,7 @@ public: //! get the current hardware mapping hint E_HARDWARE_MAPPING getHardwareMappingHint_Index() const override { - return MappingHint_Index; + return Indices->getHardwareMappingHint(); } //! set the hardware mapping hint, for driver @@ -211,7 +220,7 @@ public: if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX) Vertices->setHardwareMappingHint(NewMappingHint); if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX) - MappingHint_Index = NewMappingHint; + Indices->setHardwareMappingHint(NewMappingHint); } //! Describe what kind of primitive geometry is used by the meshbuffer @@ -232,7 +241,7 @@ public: if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX) Vertices->setDirty(); if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX) - ++ChangedID_Index; + Indices->setDirty(); } //! Get the currently used ID for identification of changes. @@ -241,7 +250,7 @@ public: //! Get the currently used ID for identification of changes. /** This shouldn't be used for anything outside the VideoDriver. */ - u32 getChangedID_Index() const override { return ChangedID_Index; } + u32 getChangedID_Index() const override { return Indices->getChangedID(); } void setHWBuffer(void *ptr) const override { @@ -253,17 +262,14 @@ public: return HWBuffer; } - u32 ChangedID_Index; - - E_HARDWARE_MAPPING MappingHint_Index; mutable void *HWBuffer; //! Material for this meshbuffer. video::SMaterial Material; //! Vertex buffer CVertexBuffer *Vertices; - //! Indices into the vertices of this buffer. - std::vector Indices; + //! Index buffer + SIndexBuffer *Indices; //! Bounding box of this meshbuffer. core::aabbox3d BoundingBox; //! Primitive type used for rendering (triangles, lines, ...) diff --git a/irr/include/IIndexBuffer.h b/irr/include/IIndexBuffer.h index 3d5b8e76a..bdbbb6dcc 100644 --- a/irr/include/IIndexBuffer.h +++ b/irr/include/IIndexBuffer.h @@ -12,40 +12,33 @@ namespace irr { -namespace video -{ - -} - namespace scene { class IIndexBuffer : public virtual IReferenceCounted { public: + //! Get type of index data which is stored in this meshbuffer. + /** \return Index type of this buffer. */ + virtual video::E_INDEX_TYPE getType() const = 0; + + //! Get access to indices. + /** \return Pointer to indices array. */ + virtual const void *getData() const = 0; + + //! Get access to indices. + /** \return Pointer to indices array. */ virtual void *getData() = 0; - virtual video::E_INDEX_TYPE getType() const = 0; - virtual void setType(video::E_INDEX_TYPE IndexType) = 0; - - virtual u32 stride() const = 0; - - virtual u32 size() const = 0; - virtual void push_back(const u32 &element) = 0; - virtual u32 operator[](u32 index) const = 0; - virtual u32 getLast() = 0; - virtual void setValue(u32 index, u32 value) = 0; - virtual void set_used(u32 usedNow) = 0; - virtual void reallocate(u32 new_size) = 0; - virtual u32 allocated_size() const = 0; - - virtual void *pointer() = 0; + //! Get amount of indices in this meshbuffer. + /** \return Number of indices in this buffer. */ + virtual u32 getCount() const = 0; //! get the current hardware mapping hint virtual E_HARDWARE_MAPPING getHardwareMappingHint() const = 0; //! set the hardware mapping hint, for driver - virtual void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint) = 0; + virtual void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint) = 0; //! flags the meshbuffer as changed, reloads hardware buffers virtual void setDirty() = 0; @@ -53,6 +46,10 @@ public: //! Get the currently used ID for identification of changes. /** This shouldn't be used for anything outside the VideoDriver. */ virtual u32 getChangedID() const = 0; + + //! Used by the VideoDriver to remember the buffer link. + virtual void setHWBuffer(void *ptr) const = 0; + virtual void *getHWBuffer() const = 0; }; } // end namespace scene diff --git a/irr/src/CBillboardSceneNode.cpp b/irr/src/CBillboardSceneNode.cpp index cf9a58818..db75a2af4 100644 --- a/irr/src/CBillboardSceneNode.cpp +++ b/irr/src/CBillboardSceneNode.cpp @@ -27,16 +27,17 @@ CBillboardSceneNode::CBillboardSceneNode(ISceneNode *parent, ISceneManager *mgr, setSize(size); auto &Vertices = Buffer->Vertices->Data; + auto &Indices = Buffer->Indices->Data; Vertices.resize(4); - Buffer->Indices.resize(6); + Indices.resize(6); - Buffer->Indices[0] = 0; - Buffer->Indices[1] = 2; - Buffer->Indices[2] = 1; - Buffer->Indices[3] = 0; - Buffer->Indices[4] = 3; - Buffer->Indices[5] = 2; + Indices[0] = 0; + Indices[1] = 2; + Indices[2] = 1; + Indices[3] = 0; + Indices[4] = 3; + Indices[5] = 2; Vertices[0].TCoords.set(1.0f, 1.0f); Vertices[0].Color = colorBottom; diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index 5abaffec8..9b2bd0cde 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -135,7 +135,7 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const auto *vt = static_cast(mb->getVertices()); buffer->VertexBuffer().insert(buffer->VertexBuffer().end(), vt, vt + mb->getVertexCount()); auto *indices = mb->getIndices(); - buffer->Indices.insert(buffer->Indices.end(), indices, indices + mb->getIndexCount()); + buffer->IndexBuffer().insert(buffer->IndexBuffer().end(), indices, indices + mb->getIndexCount()); clone->addMeshBuffer(buffer); buffer->drop(); } break; @@ -145,7 +145,7 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const auto *vt = static_cast(mb->getVertices()); buffer->VertexBuffer().insert(buffer->VertexBuffer().end(), vt, vt + mb->getVertexCount()); auto *indices = mb->getIndices(); - buffer->Indices.insert(buffer->Indices.end(), indices, indices + mb->getIndexCount()); + buffer->IndexBuffer().insert(buffer->IndexBuffer().end(), indices, indices + mb->getIndexCount()); clone->addMeshBuffer(buffer); buffer->drop(); } break; @@ -155,7 +155,7 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const auto *vt = static_cast(mb->getVertices()); buffer->VertexBuffer().insert(buffer->VertexBuffer().end(), vt, vt + mb->getVertexCount()); auto *indices = mb->getIndices(); - buffer->Indices.insert(buffer->Indices.end(), indices, indices + mb->getIndexCount()); + buffer->IndexBuffer().insert(buffer->IndexBuffer().end(), indices, indices + mb->getIndexCount()); clone->addMeshBuffer(buffer); buffer->drop(); } break; diff --git a/irr/src/COBJMeshFileLoader.cpp b/irr/src/COBJMeshFileLoader.cpp index ca382c418..064fc4186 100644 --- a/irr/src/COBJMeshFileLoader.cpp +++ b/irr/src/COBJMeshFileLoader.cpp @@ -247,15 +247,16 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file) } // triangulate the face + auto &Indices = currMtl->Meshbuffer->IndexBuffer(); const int c = faceCorners[0]; for (u32 i = 1; i < faceCorners.size() - 1; ++i) { // Add a triangle const int a = faceCorners[i + 1]; const int b = faceCorners[i]; if (a != b && a != c && b != c) { // ignore degenerated faces. We can get them when we merge vertices above in the VertMap. - currMtl->Meshbuffer->Indices.push_back(a); - currMtl->Meshbuffer->Indices.push_back(b); - currMtl->Meshbuffer->Indices.push_back(c); + Indices.push_back(a); + Indices.push_back(b); + Indices.push_back(c); } else { ++degeneratedFaces; } diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 5e4023569..b4356dc27 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -1358,11 +1358,8 @@ video::SMaterial &ClientMap::DrawDescriptor::getMaterial() u32 ClientMap::DrawDescriptor::draw(video::IVideoDriver* driver) { if (m_use_partial_buffer) { - m_partial_buffer->beforeDraw(); - driver->drawMeshBuffer(m_partial_buffer->getBuffer()); - auto count = m_partial_buffer->getBuffer()->getVertexCount(); - m_partial_buffer->afterDraw(); - return count; + m_partial_buffer->draw(driver); + return m_partial_buffer->getBuffer()->getVertexCount(); } else { driver->drawMeshBuffer(m_buffer); return m_buffer->getVertexCount(); diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 4b91b04d3..64e4b775a 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -179,6 +179,7 @@ void Clouds::updateMesh() auto *mb = m_meshbuffer.get(); auto &vertices = mb->Vertices->Data; + auto &indices = mb->Indices->Data; { const u32 vertex_count = num_faces_to_draw * 16 * m_cloud_radius_i * m_cloud_radius_i; const u32 quad_count = vertex_count / 4; @@ -186,7 +187,7 @@ void Clouds::updateMesh() // reserve memory vertices.reserve(vertex_count); - mb->Indices.reserve(index_count); + indices.reserve(index_count); } #define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius)) @@ -323,18 +324,18 @@ void Clouds::updateMesh() const u32 index_count = quad_count * 6; // rewrite index array as needed if (mb->getIndexCount() > index_count) { - mb->Indices.resize(index_count); + indices.resize(index_count); mb->setDirty(scene::EBT_INDEX); } else if (mb->getIndexCount() < index_count) { const u32 start = mb->getIndexCount() / 6; assert(start * 6 == mb->getIndexCount()); for (u32 k = start; k < quad_count; k++) { - mb->Indices.push_back(4 * k + 0); - mb->Indices.push_back(4 * k + 1); - mb->Indices.push_back(4 * k + 2); - mb->Indices.push_back(4 * k + 2); - mb->Indices.push_back(4 * k + 3); - mb->Indices.push_back(4 * k + 0); + indices.push_back(4 * k + 0); + indices.push_back(4 * k + 1); + indices.push_back(4 * k + 2); + indices.push_back(4 * k + 2); + indices.push_back(4 * k + 3); + indices.push_back(4 * k + 0); } mb->setDirty(scene::EBT_INDEX); } diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 128c3f9d9..52fbfb6b4 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -135,8 +135,9 @@ Hud::Hud(Client *client, LocalPlayer *player, // Prepare mesh for compass drawing auto &b = m_rotation_mesh_buffer; auto &vertices = b.Vertices->Data; + auto &indices = b.Indices->Data; vertices.resize(4); - b.Indices.resize(6); + indices.resize(6); video::SColor white(255, 255, 255, 255); v3f normal(0.f, 0.f, 1.f); @@ -146,12 +147,12 @@ Hud::Hud(Client *client, LocalPlayer *player, vertices[2] = video::S3DVertex(v3f( 1.f, 1.f, 0.f), normal, white, v2f(1.f, 0.f)); vertices[3] = video::S3DVertex(v3f( 1.f, -1.f, 0.f), normal, white, v2f(1.f, 1.f)); - b.Indices[0] = 0; - b.Indices[1] = 1; - b.Indices[2] = 2; - b.Indices[3] = 2; - b.Indices[4] = 3; - b.Indices[5] = 0; + indices[0] = 0; + indices[1] = 1; + indices[2] = 2; + indices[3] = 2; + indices[4] = 3; + indices[5] = 0; b.getMaterial().Lighting = false; b.getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 0638c34c9..59883d052 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -592,17 +592,14 @@ void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector &output PartialMeshBuffer */ -void PartialMeshBuffer::beforeDraw() const +void PartialMeshBuffer::draw(video::IVideoDriver *driver) const { - // Patch the indexes in the mesh buffer before draw - m_buffer->Indices = std::move(m_vertex_indexes); - m_buffer->setDirty(scene::EBT_INDEX); -} - -void PartialMeshBuffer::afterDraw() const -{ - // Take the data back - m_vertex_indexes = std::move(m_buffer->Indices); + // Swap out the index buffer before drawing the mesh + auto *old = m_buffer->Indices; + m_buffer->Indices = m_indices.get(); + m_buffer->setDirty(scene::EBT_INDEX); // TODO remove + driver->drawMeshBuffer(m_buffer); + m_buffer->Indices = old; } /* @@ -789,6 +786,7 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs } // Transparent parts have changing indices + // TODO: remove for (auto &it : m_transparent_triangles) it.buffer->setHardwareMappingHint(scene::EHM_STREAM, scene::EBT_INDEX); diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 6ddd988aa..e958814f3 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes_extrabloated.h" +#include "irr_ptr.h" #include "util/numeric.h" #include "client/tile.h" #include "voxel.h" @@ -145,25 +146,24 @@ private: * Attach alternate `Indices` to an existing mesh buffer, to make it possible to use different * indices with the same vertex buffer. * - * Irrlicht does not currently support this: `CMeshBuffer` ties together a single vertex buffer - * and a single index buffer. There's no way to share these between mesh buffers. - * */ class PartialMeshBuffer { public: - PartialMeshBuffer(scene::SMeshBuffer *buffer, std::vector &&vertex_indexes) : - m_buffer(buffer), m_vertex_indexes(std::move(vertex_indexes)) - {} + PartialMeshBuffer(scene::SMeshBuffer *buffer, std::vector &&vertex_indices) : + m_buffer(buffer) + { + m_indices.reset(new scene::SIndexBuffer()); + m_indices->Data = std::move(vertex_indices); + } scene::IMeshBuffer *getBuffer() const { return m_buffer; } - const std::vector &getVertexIndexes() const { return m_vertex_indexes; } - void beforeDraw() const; - void afterDraw() const; + void draw(video::IVideoDriver *driver) const; + private: scene::SMeshBuffer *m_buffer; - mutable std::vector m_vertex_indexes; + irr_ptr m_indices; }; /* diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 2368057fc..b2fc0882d 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -556,8 +556,9 @@ scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() { scene::SMeshBuffer *buf = new scene::SMeshBuffer(); auto &vertices = buf->Vertices->Data; + auto &indices = buf->Indices->Data; vertices.resize(4); - buf->Indices.resize(6); + indices.resize(6); static const video::SColor c(255, 255, 255, 255); vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); @@ -565,12 +566,12 @@ scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() vertices[2] = video::S3DVertex( 1, 1, 0, 0, 0, 1, c, 1, 0); vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1); - buf->Indices[0] = 0; - buf->Indices[1] = 1; - buf->Indices[2] = 2; - buf->Indices[3] = 2; - buf->Indices[4] = 3; - buf->Indices[5] = 0; + indices[0] = 0; + indices[1] = 1; + indices[2] = 2; + indices[3] = 2; + indices[4] = 3; + indices[5] = 0; buf->setHardwareMappingHint(scene::EHM_STATIC); return buf; diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 19b9765a5..f309d444c 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -831,8 +831,9 @@ void Sky::updateStars() m_star_params.count = 0x4000; } auto &vertices = m_stars->Vertices->Data; + auto &indices = m_stars->Indices->Data; vertices.reserve(4 * m_star_params.count); - m_stars->Indices.reserve(6 * m_star_params.count); + indices.reserve(6 * m_star_params.count); video::SColor fallback_color = m_star_params.starcolor; // used on GLES 2 “without shaders” PcgRandom rgen(m_seed); @@ -859,12 +860,12 @@ void Sky::updateStars() vertices.push_back(video::S3DVertex(p3, {}, fallback_color, {})); } for (u16 i = 0; i < m_star_params.count; i++) { - m_stars->Indices.push_back(i * 4 + 0); - m_stars->Indices.push_back(i * 4 + 1); - m_stars->Indices.push_back(i * 4 + 2); - m_stars->Indices.push_back(i * 4 + 2); - m_stars->Indices.push_back(i * 4 + 3); - m_stars->Indices.push_back(i * 4 + 0); + indices.push_back(i * 4 + 0); + indices.push_back(i * 4 + 1); + indices.push_back(i * 4 + 2); + indices.push_back(i * 4 + 2); + indices.push_back(i * 4 + 3); + indices.push_back(i * 4 + 0); } m_stars->setHardwareMappingHint(scene::EHM_STATIC); } From 435a89b5a4b893cbcc2a9d417fd5ac84c2caad25 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 28 Aug 2024 17:59:53 +0200 Subject: [PATCH 068/200] Apply same changes to SSkinMeshBuffer --- irr/include/SSkinMeshBuffer.h | 248 +++++++++++++++------------------ irr/src/CB3DMeshFileLoader.cpp | 27 ++-- irr/src/CXMeshFileLoader.cpp | 18 +-- 3 files changed, 135 insertions(+), 158 deletions(-) diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index 2b71cf6c5..abe077076 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -5,6 +5,8 @@ #pragma once #include "IMeshBuffer.h" +#include "CVertexBuffer.h" +#include "CIndexBuffer.h" #include "S3DVertex.h" #include "irrArray.h" @@ -18,18 +20,32 @@ struct SSkinMeshBuffer : public IMeshBuffer { //! Default constructor SSkinMeshBuffer(video::E_VERTEX_TYPE vt = video::EVT_STANDARD) : - ChangedID_Vertex(1), ChangedID_Index(1), VertexType(vt), - PrimitiveType(EPT_TRIANGLES), HWBuffer(nullptr), - MappingHint_Vertex(EHM_NEVER), MappingHint_Index(EHM_NEVER), - BoundingBoxNeedsRecalculated(true) - {} + VertexType(vt), PrimitiveType(EPT_TRIANGLES), + HWBuffer(nullptr), BoundingBoxNeedsRecalculated(true) + { +#ifdef _DEBUG + setDebugName("SSkinMeshBuffer"); +#endif + Vertices_Tangents = new SVertexBufferTangents(); + Vertices_2TCoords = new SVertexBufferLightMap(); + Vertices_Standard = new SVertexBuffer(); + Indices = new SIndexBuffer(); + } //! Constructor for standard vertices SSkinMeshBuffer(std::vector &&vertices, std::vector &&indices) : SSkinMeshBuffer() { - Vertices_Standard = std::move(vertices); - Indices = std::move(indices); + Vertices_Standard->Data = std::move(vertices); + Indices->Data = std::move(indices); + } + + ~SSkinMeshBuffer() + { + Vertices_Tangents->drop(); + Vertices_2TCoords->drop(); + Vertices_Standard->drop(); + Indices->drop(); } //! Get Material of this buffer. @@ -44,81 +60,86 @@ struct SSkinMeshBuffer : public IMeshBuffer return Material; } +protected: + const scene::IVertexBuffer *getVertexBuffer() const + { + switch (VertexType) { + case video::EVT_2TCOORDS: + return Vertices_2TCoords; + case video::EVT_TANGENTS: + return Vertices_Tangents; + default: + return Vertices_Standard; + } + } + + scene::IVertexBuffer *getVertexBuffer() + { + switch (VertexType) { + case video::EVT_2TCOORDS: + return Vertices_2TCoords; + case video::EVT_TANGENTS: + return Vertices_Tangents; + default: + return Vertices_Standard; + } + } +public: + //! Get standard vertex at given index virtual video::S3DVertex *getVertex(u32 index) { switch (VertexType) { case video::EVT_2TCOORDS: - return (video::S3DVertex *)&Vertices_2TCoords[index]; + return &Vertices_2TCoords->Data[index]; case video::EVT_TANGENTS: - return (video::S3DVertex *)&Vertices_Tangents[index]; + return &Vertices_Tangents->Data[index]; default: - return &Vertices_Standard[index]; + return &Vertices_Standard->Data[index]; } } //! Get pointer to vertex array const void *getVertices() const override { - switch (VertexType) { - case video::EVT_2TCOORDS: - return Vertices_2TCoords.data(); - case video::EVT_TANGENTS: - return Vertices_Tangents.data(); - default: - return Vertices_Standard.data(); - } + return getVertexBuffer()->getData(); } //! Get pointer to vertex array void *getVertices() override { - switch (VertexType) { - case video::EVT_2TCOORDS: - return Vertices_2TCoords.data(); - case video::EVT_TANGENTS: - return Vertices_Tangents.data(); - default: - return Vertices_Standard.data(); - } + return getVertexBuffer()->getData(); } //! Get vertex count u32 getVertexCount() const override { - switch (VertexType) { - case video::EVT_2TCOORDS: - return static_cast(Vertices_2TCoords.size()); - case video::EVT_TANGENTS: - return static_cast(Vertices_Tangents.size()); - default: - return static_cast(Vertices_Standard.size()); - } + return getVertexBuffer()->getCount(); } //! Get type of index data which is stored in this meshbuffer. /** \return Index type of this buffer. */ video::E_INDEX_TYPE getIndexType() const override { - return video::EIT_16BIT; + return Indices->getType(); } //! Get pointer to index array const u16 *getIndices() const override { - return Indices.data(); + return static_cast(Indices->getData()); } //! Get pointer to index array u16 *getIndices() override { - return Indices.data(); + return static_cast(Indices->getData()); } //! Get index count u32 getIndexCount() const override { - return static_cast(Indices.size()); + return Indices->getCount(); } //! Get bounding box @@ -143,32 +164,35 @@ struct SSkinMeshBuffer : public IMeshBuffer switch (VertexType) { case video::EVT_STANDARD: { - if (Vertices_Standard.empty()) + if (!Vertices_Standard->getCount()) BoundingBox.reset(0, 0, 0); else { - BoundingBox.reset(Vertices_Standard[0].Pos); - for (size_t i = 1; i < Vertices_Standard.size(); ++i) - BoundingBox.addInternalPoint(Vertices_Standard[i].Pos); + auto &vertices = Vertices_Standard->Data; + BoundingBox.reset(vertices[0].Pos); + for (size_t i = 1; i < vertices.size(); ++i) + BoundingBox.addInternalPoint(vertices[i].Pos); } break; } case video::EVT_2TCOORDS: { - if (Vertices_2TCoords.empty()) + if (!Vertices_2TCoords->getCount()) BoundingBox.reset(0, 0, 0); else { - BoundingBox.reset(Vertices_2TCoords[0].Pos); - for (size_t i = 1; i < Vertices_2TCoords.size(); ++i) - BoundingBox.addInternalPoint(Vertices_2TCoords[i].Pos); + auto &vertices = Vertices_2TCoords->Data; + BoundingBox.reset(vertices[0].Pos); + for (size_t i = 1; i < vertices.size(); ++i) + BoundingBox.addInternalPoint(vertices[i].Pos); } break; } case video::EVT_TANGENTS: { - if (Vertices_Tangents.empty()) + if (!Vertices_Tangents->getCount()) BoundingBox.reset(0, 0, 0); else { - BoundingBox.reset(Vertices_Tangents[0].Pos); - for (size_t i = 1; i < Vertices_Tangents.size(); ++i) - BoundingBox.addInternalPoint(Vertices_Tangents[i].Pos); + auto &vertices = Vertices_Tangents->Data; + BoundingBox.reset(vertices[0].Pos); + for (size_t i = 1; i < vertices.size(); ++i) + BoundingBox.addInternalPoint(vertices[i].Pos); } break; } @@ -185,15 +209,15 @@ struct SSkinMeshBuffer : public IMeshBuffer void convertTo2TCoords() { if (VertexType == video::EVT_STANDARD) { - for (const auto &Vertex_Standard : Vertices_Standard) { - video::S3DVertex2TCoords Vertex; + video::S3DVertex2TCoords Vertex; + for (const auto &Vertex_Standard : Vertices_Standard->Data) { Vertex.Color = Vertex_Standard.Color; Vertex.Pos = Vertex_Standard.Pos; Vertex.Normal = Vertex_Standard.Normal; Vertex.TCoords = Vertex_Standard.TCoords; - Vertices_2TCoords.push_back(Vertex); + Vertices_2TCoords->Data.push_back(Vertex); } - Vertices_Standard.clear(); + Vertices_Standard->Data.clear(); VertexType = video::EVT_2TCOORDS; } } @@ -202,26 +226,26 @@ struct SSkinMeshBuffer : public IMeshBuffer void convertToTangents() { if (VertexType == video::EVT_STANDARD) { - for (const auto &Vertex_Standard : Vertices_Standard) { video::S3DVertexTangents Vertex; + for (const auto &Vertex_Standard : Vertices_Standard->Data) { Vertex.Color = Vertex_Standard.Color; Vertex.Pos = Vertex_Standard.Pos; Vertex.Normal = Vertex_Standard.Normal; Vertex.TCoords = Vertex_Standard.TCoords; - Vertices_Tangents.push_back(Vertex); + Vertices_Tangents->Data.push_back(Vertex); } - Vertices_Standard.clear(); + Vertices_Standard->Data.clear(); VertexType = video::EVT_TANGENTS; } else if (VertexType == video::EVT_2TCOORDS) { - for (const auto &Vertex_2TCoords : Vertices_2TCoords) { - video::S3DVertexTangents Vertex; + video::S3DVertexTangents Vertex; + for (const auto &Vertex_2TCoords : Vertices_2TCoords->Data) { Vertex.Color = Vertex_2TCoords.Color; Vertex.Pos = Vertex_2TCoords.Pos; Vertex.Normal = Vertex_2TCoords.Normal; Vertex.TCoords = Vertex_2TCoords.TCoords; - Vertices_Tangents.push_back(Vertex); + Vertices_Tangents->Data.push_back(Vertex); } - Vertices_2TCoords.clear(); + Vertices_2TCoords->Data.clear(); VertexType = video::EVT_TANGENTS; } } @@ -229,79 +253,37 @@ struct SSkinMeshBuffer : public IMeshBuffer //! returns position of vertex i const core::vector3df &getPosition(u32 i) const override { - switch (VertexType) { - case video::EVT_2TCOORDS: - return Vertices_2TCoords[i].Pos; - case video::EVT_TANGENTS: - return Vertices_Tangents[i].Pos; - default: - return Vertices_Standard[i].Pos; - } + return getVertexBuffer()->getPosition(i); } //! returns position of vertex i core::vector3df &getPosition(u32 i) override { - switch (VertexType) { - case video::EVT_2TCOORDS: - return Vertices_2TCoords[i].Pos; - case video::EVT_TANGENTS: - return Vertices_Tangents[i].Pos; - default: - return Vertices_Standard[i].Pos; - } + return getVertexBuffer()->getPosition(i); } //! returns normal of vertex i const core::vector3df &getNormal(u32 i) const override { - switch (VertexType) { - case video::EVT_2TCOORDS: - return Vertices_2TCoords[i].Normal; - case video::EVT_TANGENTS: - return Vertices_Tangents[i].Normal; - default: - return Vertices_Standard[i].Normal; - } + return getVertexBuffer()->getNormal(i); } //! returns normal of vertex i core::vector3df &getNormal(u32 i) override { - switch (VertexType) { - case video::EVT_2TCOORDS: - return Vertices_2TCoords[i].Normal; - case video::EVT_TANGENTS: - return Vertices_Tangents[i].Normal; - default: - return Vertices_Standard[i].Normal; - } + return getVertexBuffer()->getNormal(i); } //! returns texture coords of vertex i const core::vector2df &getTCoords(u32 i) const override { - switch (VertexType) { - case video::EVT_2TCOORDS: - return Vertices_2TCoords[i].TCoords; - case video::EVT_TANGENTS: - return Vertices_Tangents[i].TCoords; - default: - return Vertices_Standard[i].TCoords; - } + return getVertexBuffer()->getTCoords(i); } //! returns texture coords of vertex i core::vector2df &getTCoords(u32 i) override { - switch (VertexType) { - case video::EVT_2TCOORDS: - return Vertices_2TCoords[i].TCoords; - case video::EVT_TANGENTS: - return Vertices_Tangents[i].TCoords; - default: - return Vertices_Standard[i].TCoords; - } + return getVertexBuffer()->getTCoords(i); } //! append the vertices and indices to the current buffer @@ -313,26 +295,22 @@ struct SSkinMeshBuffer : public IMeshBuffer //! get the current hardware mapping hint for vertex buffers E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const override { - return MappingHint_Vertex; + return getVertexBuffer()->getHardwareMappingHint(); } //! get the current hardware mapping hint for index buffers E_HARDWARE_MAPPING getHardwareMappingHint_Index() const override { - return MappingHint_Index; + return Indices->getHardwareMappingHint(); } //! set the hardware mapping hint, for driver void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint, E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override { - if (Buffer == EBT_VERTEX) - MappingHint_Vertex = NewMappingHint; - else if (Buffer == EBT_INDEX) - MappingHint_Index = NewMappingHint; - else if (Buffer == EBT_VERTEX_AND_INDEX) { - MappingHint_Vertex = NewMappingHint; - MappingHint_Index = NewMappingHint; - } + if (Buffer == EBT_VERTEX || Buffer == EBT_VERTEX_AND_INDEX) + getVertexBuffer()->setHardwareMappingHint(NewMappingHint); + if (Buffer == EBT_INDEX || Buffer == EBT_VERTEX_AND_INDEX) + Indices->setHardwareMappingHint(NewMappingHint); } //! Describe what kind of primitive geometry is used by the meshbuffer @@ -351,14 +329,20 @@ struct SSkinMeshBuffer : public IMeshBuffer void setDirty(E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override { if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX) - ++ChangedID_Vertex; + getVertexBuffer()->setDirty(); if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX) - ++ChangedID_Index; + Indices->setDirty(); } - u32 getChangedID_Vertex() const override { return ChangedID_Vertex; } + u32 getChangedID_Vertex() const override + { + return getVertexBuffer()->getChangedID(); + } - u32 getChangedID_Index() const override { return ChangedID_Index; } + u32 getChangedID_Index() const override + { + return Indices->getChangedID(); + } void setHWBuffer(void *ptr) const override { @@ -373,15 +357,11 @@ struct SSkinMeshBuffer : public IMeshBuffer //! Call this after changing the positions of any vertex. void boundingBoxNeedsRecalculated(void) { BoundingBoxNeedsRecalculated = true; } - std::vector Vertices_Tangents; - std::vector Vertices_2TCoords; - std::vector Vertices_Standard; - std::vector Indices; + SVertexBufferTangents *Vertices_Tangents; + SVertexBufferLightMap *Vertices_2TCoords; + SVertexBuffer *Vertices_Standard; + SIndexBuffer *Indices; - u32 ChangedID_Vertex; - u32 ChangedID_Index; - - // ISkinnedMesh::SJoint *AttachedJoint; core::matrix4 Transformation; video::SMaterial Material; @@ -394,11 +374,7 @@ struct SSkinMeshBuffer : public IMeshBuffer mutable void *HWBuffer; - // hardware mapping hint - E_HARDWARE_MAPPING MappingHint_Vertex : 3; - E_HARDWARE_MAPPING MappingHint_Index : 3; - - bool BoundingBoxNeedsRecalculated : 1; + bool BoundingBoxNeedsRecalculated; }; } // end namespace scene diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index 008169bd7..4cd7b1d82 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -259,14 +259,15 @@ bool CB3DMeshFileLoader::readChunkMESH(CSkinnedMesh::SJoint *inJoint) if (!NormalsInFile) { s32 i; - for (i = 0; i < (s32)meshBuffer->Indices.size(); i += 3) { - core::plane3df p(meshBuffer->getVertex(meshBuffer->Indices[i + 0])->Pos, - meshBuffer->getVertex(meshBuffer->Indices[i + 1])->Pos, - meshBuffer->getVertex(meshBuffer->Indices[i + 2])->Pos); + auto &indices = meshBuffer->Indices->Data; + for (i = 0; i < (s32)indices.size(); i += 3) { + core::plane3df p(meshBuffer->getVertex(indices[i + 0])->Pos, + meshBuffer->getVertex(indices[i + 1])->Pos, + meshBuffer->getVertex(indices[i + 2])->Pos); - meshBuffer->getVertex(meshBuffer->Indices[i + 0])->Normal += p.Normal; - meshBuffer->getVertex(meshBuffer->Indices[i + 1])->Normal += p.Normal; - meshBuffer->getVertex(meshBuffer->Indices[i + 2])->Normal += p.Normal; + meshBuffer->getVertex(indices[i + 0])->Normal += p.Normal; + meshBuffer->getVertex(indices[i + 1])->Normal += p.Normal; + meshBuffer->getVertex(indices[i + 2])->Normal += p.Normal; } for (i = 0; i < (s32)meshBuffer->getVertexCount(); ++i) { @@ -433,7 +434,7 @@ bool CB3DMeshFileLoader::readChunkTRIS(scene::SSkinMeshBuffer *meshBuffer, u32 m } const s32 memoryNeeded = B3dStack.getLast().length / sizeof(s32); - meshBuffer->Indices.reserve(memoryNeeded + meshBuffer->Indices.size() + 1); + meshBuffer->Indices->Data.reserve(memoryNeeded + meshBuffer->Indices->Data.size() + 1); while ((B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) // this chunk repeats { @@ -471,9 +472,9 @@ bool CB3DMeshFileLoader::readChunkTRIS(scene::SSkinMeshBuffer *meshBuffer, u32 m // Add the vertex to the meshbuffer: if (meshBuffer->VertexType == video::EVT_STANDARD) - meshBuffer->Vertices_Standard.push_back(BaseVertices[vertex_id[i]]); + meshBuffer->Vertices_Standard->Data.push_back(BaseVertices[vertex_id[i]]); else - meshBuffer->Vertices_2TCoords.push_back(BaseVertices[vertex_id[i]]); + meshBuffer->Vertices_2TCoords->Data.push_back(BaseVertices[vertex_id[i]]); // create vertex id to meshbuffer index link: AnimatedVertices_VertexID[vertex_id[i]] = meshBuffer->getVertexCount() - 1; @@ -504,9 +505,9 @@ bool CB3DMeshFileLoader::readChunkTRIS(scene::SSkinMeshBuffer *meshBuffer, u32 m } } - meshBuffer->Indices.push_back(AnimatedVertices_VertexID[vertex_id[0]]); - meshBuffer->Indices.push_back(AnimatedVertices_VertexID[vertex_id[1]]); - meshBuffer->Indices.push_back(AnimatedVertices_VertexID[vertex_id[2]]); + meshBuffer->Indices->Data.push_back(AnimatedVertices_VertexID[vertex_id[0]]); + meshBuffer->Indices->Data.push_back(AnimatedVertices_VertexID[vertex_id[1]]); + meshBuffer->Indices->Data.push_back(AnimatedVertices_VertexID[vertex_id[2]]); } B3dStack.erase(B3dStack.size() - 1); diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index 4e11fe352..483955805 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -273,12 +273,12 @@ bool CXMeshFileLoader::load(io::IReadFile *file) } if (mesh->TCoords2.size()) { for (i = 0; i != mesh->Buffers.size(); ++i) { - mesh->Buffers[i]->Vertices_2TCoords.reserve(vCountArray[i]); + mesh->Buffers[i]->Vertices_2TCoords->Data.reserve(vCountArray[i]); mesh->Buffers[i]->VertexType = video::EVT_2TCOORDS; } } else { for (i = 0; i != mesh->Buffers.size(); ++i) - mesh->Buffers[i]->Vertices_Standard.reserve(vCountArray[i]); + mesh->Buffers[i]->Vertices_Standard->Data.reserve(vCountArray[i]); } verticesLinkIndex.set_used(mesh->Vertices.size()); @@ -290,14 +290,14 @@ bool CXMeshFileLoader::load(io::IReadFile *file) scene::SSkinMeshBuffer *buffer = mesh->Buffers[verticesLinkBuffer[i]]; if (mesh->TCoords2.size()) { - verticesLinkIndex[i] = buffer->Vertices_2TCoords.size(); - buffer->Vertices_2TCoords.emplace_back(mesh->Vertices[i]); + verticesLinkIndex[i] = buffer->Vertices_2TCoords->getCount(); + buffer->Vertices_2TCoords->Data.emplace_back(mesh->Vertices[i]); // We have a problem with correct tcoord2 handling here // crash fixed for now by checking the values - buffer->Vertices_2TCoords.back().TCoords2 = (i < mesh->TCoords2.size()) ? mesh->TCoords2[i] : mesh->Vertices[i].TCoords; + buffer->Vertices_2TCoords->Data.back().TCoords2 = (i < mesh->TCoords2.size()) ? mesh->TCoords2[i] : mesh->Vertices[i].TCoords; } else { - verticesLinkIndex[i] = buffer->Vertices_Standard.size(); - buffer->Vertices_Standard.push_back(mesh->Vertices[i]); + verticesLinkIndex[i] = buffer->Vertices_Standard->getCount(); + buffer->Vertices_Standard->Data.push_back(mesh->Vertices[i]); } } @@ -306,13 +306,13 @@ bool CXMeshFileLoader::load(io::IReadFile *file) for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) ++vCountArray[mesh->FaceMaterialIndices[i]]; for (i = 0; i != mesh->Buffers.size(); ++i) - mesh->Buffers[i]->Indices.reserve(vCountArray[i]); + mesh->Buffers[i]->Indices->Data.reserve(vCountArray[i]); delete[] vCountArray; // create indices per buffer for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) { scene::SSkinMeshBuffer *buffer = mesh->Buffers[mesh->FaceMaterialIndices[i]]; for (u32 id = i * 3 + 0; id != i * 3 + 3; ++id) { - buffer->Indices.push_back(verticesLinkIndex[mesh->Indices[id]]); + buffer->Indices->Data.push_back(verticesLinkIndex[mesh->Indices[id]]); } } } From be9aa19208a46ee3dccdad9890aed69656079489 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 28 Aug 2024 18:08:59 +0200 Subject: [PATCH 069/200] Propagate changes to IMeshBuffer parent class --- irr/include/CMeshBuffer.h | 132 ++--------------------- irr/include/IMeshBuffer.h | 198 ++++++++++++++++++++++++---------- irr/include/SSkinMeshBuffer.h | 139 ++---------------------- 3 files changed, 163 insertions(+), 306 deletions(-) diff --git a/irr/include/CMeshBuffer.h b/irr/include/CMeshBuffer.h index 0b47494cd..6b13a8982 100644 --- a/irr/include/CMeshBuffer.h +++ b/irr/include/CMeshBuffer.h @@ -49,25 +49,24 @@ public: return Material; } - //! Get pointer to vertices - /** \return Pointer to vertices. */ - const void *getVertices() const override + const scene::IVertexBuffer *getVertexBuffer() const override { - return Vertices->getData(); + return Vertices; } - //! Get pointer to vertices - /** \return Pointer to vertices. */ - void *getVertices() override + scene::IVertexBuffer *getVertexBuffer() override { - return Vertices->getData(); + return Vertices; } - //! Get number of vertices - /** \return Number of vertices. */ - u32 getVertexCount() const override + const scene::IIndexBuffer *getIndexBuffer() const override { - return Vertices->getCount(); + return Indices; + } + + scene::IIndexBuffer *getIndexBuffer() override + { + return Indices; } // TEMPORARY helper for direct buffer acess @@ -76,34 +75,6 @@ public: return Vertices->Data; } - //! Get type of index data which is stored in this meshbuffer. - /** \return Index type of this buffer. */ - video::E_INDEX_TYPE getIndexType() const override - { - return Indices->getType(); - } - - //! Get pointer to indices - /** \return Pointer to indices. */ - const u16 *getIndices() const override - { - return static_cast(Indices->getData()); - } - - //! Get pointer to indices - /** \return Pointer to indices. */ - u16 *getIndices() override - { - return static_cast(Indices->getData()); - } - - //! Get number of indices - /** \return Number of indices. */ - u32 getIndexCount() const override - { - return Indices->getCount(); - } - // TEMPORARY helper for direct buffer acess inline auto &IndexBuffer() { @@ -138,49 +109,6 @@ public: BoundingBox.reset(0, 0, 0); } - //! Get type of vertex data stored in this buffer. - /** \return Type of vertex data. */ - video::E_VERTEX_TYPE getVertexType() const override - { - return Vertices->getType(); - } - - //! returns position of vertex i - const core::vector3df &getPosition(u32 i) const override - { - return Vertices->getPosition(i); - } - - //! returns position of vertex i - core::vector3df &getPosition(u32 i) override - { - return Vertices->getPosition(i); - } - - //! returns normal of vertex i - const core::vector3df &getNormal(u32 i) const override - { - return Vertices->getNormal(i); - } - - //! returns normal of vertex i - core::vector3df &getNormal(u32 i) override - { - return Vertices->getNormal(i); - } - - //! returns texture coord of vertex i - const core::vector2df &getTCoords(u32 i) const override - { - return Vertices->getTCoords(i); - } - - //! returns texture coord of vertex i - core::vector2df &getTCoords(u32 i) override - { - return Vertices->getTCoords(i); - } - //! Append the vertices and indices to the current buffer void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) override { @@ -202,27 +130,6 @@ public: } } - //! get the current hardware mapping hint - E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const override - { - return Vertices->getHardwareMappingHint(); - } - - //! get the current hardware mapping hint - E_HARDWARE_MAPPING getHardwareMappingHint_Index() const override - { - return Indices->getHardwareMappingHint(); - } - - //! set the hardware mapping hint, for driver - void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint, E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override - { - if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX) - Vertices->setHardwareMappingHint(NewMappingHint); - if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX) - Indices->setHardwareMappingHint(NewMappingHint); - } - //! Describe what kind of primitive geometry is used by the meshbuffer void setPrimitiveType(E_PRIMITIVE_TYPE type) override { @@ -235,23 +142,6 @@ public: return PrimitiveType; } - //! flags the mesh as changed, reloads hardware buffers - void setDirty(E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override - { - if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX) - Vertices->setDirty(); - if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX) - Indices->setDirty(); - } - - //! Get the currently used ID for identification of changes. - /** This shouldn't be used for anything outside the VideoDriver. */ - u32 getChangedID_Vertex() const override { return Vertices->getChangedID(); } - - //! Get the currently used ID for identification of changes. - /** This shouldn't be used for anything outside the VideoDriver. */ - u32 getChangedID_Index() const override { return Indices->getChangedID(); } - void setHWBuffer(void *ptr) const override { HWBuffer = ptr; diff --git a/irr/include/IMeshBuffer.h b/irr/include/IMeshBuffer.h index c69a12d1d..8a43db87e 100644 --- a/irr/include/IMeshBuffer.h +++ b/irr/include/IMeshBuffer.h @@ -7,8 +7,8 @@ #include "IReferenceCounted.h" #include "SMaterial.h" #include "aabbox3d.h" -#include "S3DVertex.h" -#include "SVertexIndex.h" +#include "IVertexBuffer.h" +#include "IIndexBuffer.h" #include "EHardwareBufferFlags.h" #include "EPrimitiveTypes.h" @@ -46,39 +46,17 @@ public: /** \return Material of this buffer. */ virtual const video::SMaterial &getMaterial() const = 0; - //! Get type of vertex data which is stored in this meshbuffer. - /** \return Vertex type of this buffer. */ - virtual video::E_VERTEX_TYPE getVertexType() const = 0; + /// Get the vertex buffer + virtual const scene::IVertexBuffer *getVertexBuffer() const = 0; - //! Get access to vertex data. The data is an array of vertices. - /** Which vertex type is used can be determined by getVertexType(). - \return Pointer to array of vertices. */ - virtual const void *getVertices() const = 0; + /// Get the vertex buffer + virtual scene::IVertexBuffer *getVertexBuffer() = 0; - //! Get access to vertex data. The data is an array of vertices. - /** Which vertex type is used can be determined by getVertexType(). - \return Pointer to array of vertices. */ - virtual void *getVertices() = 0; + /// Get the index buffer + virtual const scene::IIndexBuffer *getIndexBuffer() const = 0; - //! Get amount of vertices in meshbuffer. - /** \return Number of vertices in this buffer. */ - virtual u32 getVertexCount() const = 0; - - //! Get type of index data which is stored in this meshbuffer. - /** \return Index type of this buffer. */ - virtual video::E_INDEX_TYPE getIndexType() const = 0; - - //! Get access to indices. - /** \return Pointer to indices array. */ - virtual const u16 *getIndices() const = 0; - - //! Get access to indices. - /** \return Pointer to indices array. */ - virtual u16 *getIndices() = 0; - - //! Get amount of indices in this meshbuffer. - /** \return Number of indices in this buffer. */ - virtual u32 getIndexCount() const = 0; + /// Get the index buffer + virtual scene::IIndexBuffer *getIndexBuffer() = 0; //! Get the axis aligned bounding box of this meshbuffer. /** \return Axis aligned bounding box of this buffer. */ @@ -92,24 +70,6 @@ public: //! Recalculates the bounding box. Should be called if the mesh changed. virtual void recalculateBoundingBox() = 0; - //! returns position of vertex i - virtual const core::vector3df &getPosition(u32 i) const = 0; - - //! returns position of vertex i - virtual core::vector3df &getPosition(u32 i) = 0; - - //! returns normal of vertex i - virtual const core::vector3df &getNormal(u32 i) const = 0; - - //! returns normal of vertex i - virtual core::vector3df &getNormal(u32 i) = 0; - - //! returns texture coord of vertex i - virtual const core::vector2df &getTCoords(u32 i) const = 0; - - //! returns texture coord of vertex i - virtual core::vector2df &getTCoords(u32 i) = 0; - //! Append the vertices and indices to the current buffer /** Only works for compatible vertex types. \param vertices Pointer to a vertex array. @@ -118,25 +78,149 @@ public: \param numIndices Number of indices in array. */ virtual void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) = 0; - //! get the current hardware mapping hint - virtual E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const = 0; + /* Leftover functions that are now just helpers for accessing the respective buffer. */ + + //! Get type of vertex data which is stored in this meshbuffer. + /** \return Vertex type of this buffer. */ + inline video::E_VERTEX_TYPE getVertexType() const + { + return getVertexBuffer()->getType(); + } + + //! Get access to vertex data. The data is an array of vertices. + /** Which vertex type is used can be determined by getVertexType(). + \return Pointer to array of vertices. */ + inline const void *getVertices() const + { + return getVertexBuffer()->getData(); + } + + //! Get access to vertex data. The data is an array of vertices. + /** Which vertex type is used can be determined by getVertexType(). + \return Pointer to array of vertices. */ + inline void *getVertices() + { + return getVertexBuffer()->getData(); + } + + //! Get amount of vertices in meshbuffer. + /** \return Number of vertices in this buffer. */ + inline u32 getVertexCount() const + { + return getVertexBuffer()->getCount(); + } + + //! Get type of index data which is stored in this meshbuffer. + /** \return Index type of this buffer. */ + inline video::E_INDEX_TYPE getIndexType() const + { + return getIndexBuffer()->getType(); + } + + //! Get access to indices. + /** \return Pointer to indices array. */ + inline const u16 *getIndices() const + { + _IRR_DEBUG_BREAK_IF(getIndexBuffer()->getType() != video::EIT_16BIT); + return static_cast(getIndexBuffer()->getData()); + } + + //! Get access to indices. + /** \return Pointer to indices array. */ + inline u16 *getIndices() + { + _IRR_DEBUG_BREAK_IF(getIndexBuffer()->getType() != video::EIT_16BIT); + return static_cast(getIndexBuffer()->getData()); + } + + //! Get amount of indices in this meshbuffer. + /** \return Number of indices in this buffer. */ + inline u32 getIndexCount() const + { + return getIndexBuffer()->getCount(); + } + + //! returns position of vertex i + inline const core::vector3df &getPosition(u32 i) const + { + return getVertexBuffer()->getPosition(i); + } + + //! returns position of vertex i + inline core::vector3df &getPosition(u32 i) + { + return getVertexBuffer()->getPosition(i); + } + + //! returns normal of vertex i + inline const core::vector3df &getNormal(u32 i) const + { + return getVertexBuffer()->getNormal(i); + } + + //! returns normal of vertex i + inline core::vector3df &getNormal(u32 i) + { + return getVertexBuffer()->getNormal(i); + } + + //! returns texture coord of vertex i + inline const core::vector2df &getTCoords(u32 i) const + { + return getVertexBuffer()->getTCoords(i); + } + + //! returns texture coord of vertex i + inline core::vector2df &getTCoords(u32 i) + { + return getVertexBuffer()->getTCoords(i); + } //! get the current hardware mapping hint - virtual E_HARDWARE_MAPPING getHardwareMappingHint_Index() const = 0; + inline E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const + { + return getVertexBuffer()->getHardwareMappingHint(); + } + + //! get the current hardware mapping hint + inline E_HARDWARE_MAPPING getHardwareMappingHint_Index() const + { + return getIndexBuffer()->getHardwareMappingHint(); + } //! set the hardware mapping hint, for driver - virtual void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0; + inline void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) + { + if (buffer == EBT_VERTEX_AND_INDEX || buffer == EBT_VERTEX) + getVertexBuffer()->setHardwareMappingHint(newMappingHint); + if (buffer == EBT_VERTEX_AND_INDEX || buffer == EBT_INDEX) + getIndexBuffer()->setHardwareMappingHint(newMappingHint); + } //! flags the meshbuffer as changed, reloads hardware buffers - virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0; + inline void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) + { + if (buffer == EBT_VERTEX_AND_INDEX || buffer == EBT_VERTEX) + getVertexBuffer()->setDirty(); + if (buffer == EBT_VERTEX_AND_INDEX || buffer == EBT_INDEX) + getIndexBuffer()->setDirty(); + } //! Get the currently used ID for identification of changes. /** This shouldn't be used for anything outside the VideoDriver. */ - virtual u32 getChangedID_Vertex() const = 0; + inline u32 getChangedID_Vertex() const + { + return getVertexBuffer()->getChangedID(); + } //! Get the currently used ID for identification of changes. /** This shouldn't be used for anything outside the VideoDriver. */ - virtual u32 getChangedID_Index() const = 0; + inline u32 getChangedID_Index() const + { + return getIndexBuffer()->getChangedID(); + } + + /* End helpers */ //! Used by the VideoDriver to remember the buffer link. virtual void setHWBuffer(void *ptr) const = 0; diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index abe077076..949551161 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -60,8 +60,7 @@ struct SSkinMeshBuffer : public IMeshBuffer return Material; } -protected: - const scene::IVertexBuffer *getVertexBuffer() const + const scene::IVertexBuffer *getVertexBuffer() const override { switch (VertexType) { case video::EVT_2TCOORDS: @@ -84,7 +83,16 @@ protected: return Vertices_Standard; } } -public: + + const scene::IIndexBuffer *getIndexBuffer() const override + { + return Indices; + } + + scene::IIndexBuffer *getIndexBuffer() override + { + return Indices; + } //! Get standard vertex at given index virtual video::S3DVertex *getVertex(u32 index) @@ -99,49 +107,6 @@ public: } } - //! Get pointer to vertex array - const void *getVertices() const override - { - return getVertexBuffer()->getData(); - } - - //! Get pointer to vertex array - void *getVertices() override - { - return getVertexBuffer()->getData(); - } - - //! Get vertex count - u32 getVertexCount() const override - { - return getVertexBuffer()->getCount(); - } - - //! Get type of index data which is stored in this meshbuffer. - /** \return Index type of this buffer. */ - video::E_INDEX_TYPE getIndexType() const override - { - return Indices->getType(); - } - - //! Get pointer to index array - const u16 *getIndices() const override - { - return static_cast(Indices->getData()); - } - - //! Get pointer to index array - u16 *getIndices() override - { - return static_cast(Indices->getData()); - } - - //! Get index count - u32 getIndexCount() const override - { - return Indices->getCount(); - } - //! Get bounding box const core::aabbox3d &getBoundingBox() const override { @@ -199,12 +164,6 @@ public: } } - //! Get vertex type - video::E_VERTEX_TYPE getVertexType() const override - { - return VertexType; - } - //! Convert to 2tcoords vertex type void convertTo2TCoords() { @@ -250,69 +209,12 @@ public: } } - //! returns position of vertex i - const core::vector3df &getPosition(u32 i) const override - { - return getVertexBuffer()->getPosition(i); - } - - //! returns position of vertex i - core::vector3df &getPosition(u32 i) override - { - return getVertexBuffer()->getPosition(i); - } - - //! returns normal of vertex i - const core::vector3df &getNormal(u32 i) const override - { - return getVertexBuffer()->getNormal(i); - } - - //! returns normal of vertex i - core::vector3df &getNormal(u32 i) override - { - return getVertexBuffer()->getNormal(i); - } - - //! returns texture coords of vertex i - const core::vector2df &getTCoords(u32 i) const override - { - return getVertexBuffer()->getTCoords(i); - } - - //! returns texture coords of vertex i - core::vector2df &getTCoords(u32 i) override - { - return getVertexBuffer()->getTCoords(i); - } - //! append the vertices and indices to the current buffer void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) override { _IRR_DEBUG_BREAK_IF(true); } - //! get the current hardware mapping hint for vertex buffers - E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const override - { - return getVertexBuffer()->getHardwareMappingHint(); - } - - //! get the current hardware mapping hint for index buffers - E_HARDWARE_MAPPING getHardwareMappingHint_Index() const override - { - return Indices->getHardwareMappingHint(); - } - - //! set the hardware mapping hint, for driver - void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint, E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override - { - if (Buffer == EBT_VERTEX || Buffer == EBT_VERTEX_AND_INDEX) - getVertexBuffer()->setHardwareMappingHint(NewMappingHint); - if (Buffer == EBT_INDEX || Buffer == EBT_VERTEX_AND_INDEX) - Indices->setHardwareMappingHint(NewMappingHint); - } - //! Describe what kind of primitive geometry is used by the meshbuffer void setPrimitiveType(E_PRIMITIVE_TYPE type) override { @@ -325,25 +227,6 @@ public: return PrimitiveType; } - //! flags the mesh as changed, reloads hardware buffers - void setDirty(E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override - { - if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX) - getVertexBuffer()->setDirty(); - if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX) - Indices->setDirty(); - } - - u32 getChangedID_Vertex() const override - { - return getVertexBuffer()->getChangedID(); - } - - u32 getChangedID_Index() const override - { - return Indices->getChangedID(); - } - void setHWBuffer(void *ptr) const override { HWBuffer = ptr; From 6b7fc1e9fe68960f76f3a5142a994a5dd36a90a6 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 28 Aug 2024 19:35:28 +0200 Subject: [PATCH 070/200] Handle vertex & index VBOs separately in GL drivers --- irr/include/IVideoDriver.h | 18 ++- irr/src/CNullDriver.cpp | 83 ++++++++++---- irr/src/CNullDriver.h | 86 +++++++++----- irr/src/COpenGLDriver.cpp | 165 ++++++++++++++------------- irr/src/COpenGLDriver.h | 23 ++-- irr/src/OpenGL/Driver.cpp | 224 ++++++++++++++++--------------------- irr/src/OpenGL/Driver.h | 30 +++-- 7 files changed, 346 insertions(+), 283 deletions(-) diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index 6d2182e8a..b3312160c 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -31,6 +31,8 @@ class IWriteFile; namespace scene { class IMeshBuffer; +class IVertexBuffer; +class IIndexBuffer; class IMesh; class IMeshManipulator; class ISceneNode; @@ -299,7 +301,10 @@ public: virtual void removeAllTextures() = 0; //! Remove hardware buffer - virtual void removeHardwareBuffer(const scene::IMeshBuffer *mb) = 0; + virtual void removeHardwareBuffer(const scene::IVertexBuffer *vb) = 0; + + //! Remove hardware buffer + virtual void removeHardwareBuffer(const scene::IIndexBuffer *ib) = 0; //! Remove all hardware buffers virtual void removeAllHardwareBuffers() = 0; @@ -738,6 +743,17 @@ public: /** \param mb Buffer to draw */ virtual void drawMeshBuffer(const scene::IMeshBuffer *mb) = 0; + /** + * Draws a mesh from individual vertex and index buffers. + * @param vb vertices to use + * @param ib indices to use + * @param primCount amount of primitives + * @param pType primitive type + */ + virtual void drawBuffers(const scene::IVertexBuffer *vb, + const scene::IIndexBuffer *ib, u32 primCount, + scene::E_PRIMITIVE_TYPE pType = scene::EPT_TRIANGLES) = 0; + //! Draws normals of a mesh buffer /** \param mb Buffer to draw the normals of \param length length scale factor of the normals diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index 054267711..6f261cef1 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -1109,19 +1109,20 @@ void CNullDriver::getFog(SColor &color, E_FOG_TYPE &fogType, f32 &start, f32 &en rangeFog = RangeFog; } -//! Draws a mesh buffer -void CNullDriver::drawMeshBuffer(const scene::IMeshBuffer *mb) +void CNullDriver::drawBuffers(const scene::IVertexBuffer *vb, + const scene::IIndexBuffer *ib, u32 primCount, + scene::E_PRIMITIVE_TYPE pType) { - if (!mb) + if (!vb || !ib) return; - // IVertexBuffer and IIndexBuffer later - SHWBufferLink *HWBuffer = getBufferLink(mb); + if (vb->getHWBuffer() || ib->getHWBuffer()) { + // subclass is supposed to override this if it supports hw buffers + _IRR_DEBUG_BREAK_IF(1); + } - if (HWBuffer) - drawHardwareBuffer(HWBuffer); - else - drawVertexPrimitiveList(mb->getVertices(), mb->getVertexCount(), mb->getIndices(), mb->getPrimitiveCount(), mb->getVertexType(), mb->getPrimitiveType(), mb->getIndexType()); + drawVertexPrimitiveList(vb->getData(), vb->getCount(), ib->getData(), + primCount, vb->getType(), pType, ib->getType()); } //! Draws the normals of a mesh buffer @@ -1140,17 +1141,30 @@ void CNullDriver::drawMeshBufferNormals(const scene::IMeshBuffer *mb, f32 length } } -CNullDriver::SHWBufferLink *CNullDriver::getBufferLink(const scene::IMeshBuffer *mb) +CNullDriver::SHWBufferLink *CNullDriver::getBufferLink(const scene::IVertexBuffer *vb) { - if (!mb || !isHardwareBufferRecommend(mb)) + if (!vb || !isHardwareBufferRecommend(vb)) return 0; // search for hardware links - SHWBufferLink *HWBuffer = reinterpret_cast(mb->getHWBuffer()); + SHWBufferLink *HWBuffer = reinterpret_cast(vb->getHWBuffer()); if (HWBuffer) return HWBuffer; - return createHardwareBuffer(mb); // no hardware links, and mesh wants one, create it + return createHardwareBuffer(vb); // no hardware links, and mesh wants one, create it +} + +CNullDriver::SHWBufferLink *CNullDriver::getBufferLink(const scene::IIndexBuffer *ib) +{ + if (!ib || !isHardwareBufferRecommend(ib)) + return 0; + + // search for hardware links + SHWBufferLink *HWBuffer = reinterpret_cast(ib->getHWBuffer()); + if (HWBuffer) + return HWBuffer; + + return createHardwareBuffer(ib); // no hardware links, and mesh wants one, create it } //! Update all hardware buffers, remove unused ones @@ -1161,8 +1175,13 @@ void CNullDriver::updateAllHardwareBuffers() SHWBufferLink *Link = *it; ++it; - if (!Link->MeshBuffer || Link->MeshBuffer->getReferenceCount() == 1) - deleteHardwareBuffer(Link); + if (Link->IsVertex) { + if (!Link->VertexBuffer || Link->VertexBuffer->getReferenceCount() == 1) + deleteHardwareBuffer(Link); + } else { + if (!Link->IndexBuffer || Link->IndexBuffer->getReferenceCount() == 1) + deleteHardwareBuffer(Link); + } } } @@ -1174,12 +1193,20 @@ void CNullDriver::deleteHardwareBuffer(SHWBufferLink *HWBuffer) delete HWBuffer; } -//! Remove hardware buffer -void CNullDriver::removeHardwareBuffer(const scene::IMeshBuffer *mb) +void CNullDriver::removeHardwareBuffer(const scene::IVertexBuffer *vb) { - if (!mb) + if (!vb) return; - SHWBufferLink *HWBuffer = reinterpret_cast(mb->getHWBuffer()); + SHWBufferLink *HWBuffer = reinterpret_cast(vb->getHWBuffer()); + if (HWBuffer) + deleteHardwareBuffer(HWBuffer); +} + +void CNullDriver::removeHardwareBuffer(const scene::IIndexBuffer *ib) +{ + if (!ib) + return; + SHWBufferLink *HWBuffer = reinterpret_cast(ib->getHWBuffer()); if (HWBuffer) deleteHardwareBuffer(HWBuffer); } @@ -1191,12 +1218,24 @@ void CNullDriver::removeAllHardwareBuffers() deleteHardwareBuffer(HWBufferList.front()); } -bool CNullDriver::isHardwareBufferRecommend(const scene::IMeshBuffer *mb) +bool CNullDriver::isHardwareBufferRecommend(const scene::IVertexBuffer *vb) { - if (!mb || (mb->getHardwareMappingHint_Index() == scene::EHM_NEVER && mb->getHardwareMappingHint_Vertex() == scene::EHM_NEVER)) + if (!vb || vb->getHardwareMappingHint() == scene::EHM_NEVER) return false; - if (mb->getVertexCount() < MinVertexCountForVBO) + if (vb->getCount() < MinVertexCountForVBO) + return false; + + return true; +} + +bool CNullDriver::isHardwareBufferRecommend(const scene::IIndexBuffer *ib) +{ + if (!ib || ib->getHardwareMappingHint() == scene::EHM_NEVER) + return false; + + // This is a bit stupid + if (ib->getCount() < MinVertexCountForVBO * 3) return false; return true; diff --git a/irr/src/CNullDriver.h b/irr/src/CNullDriver.h index 302108a1b..b8d45118f 100644 --- a/irr/src/CNullDriver.h +++ b/irr/src/CNullDriver.h @@ -269,8 +269,18 @@ public: const core::position2d &pos, const core::dimension2d &size) override; - //! Draws a mesh buffer - void drawMeshBuffer(const scene::IMeshBuffer *mb) override; + void drawMeshBuffer(const scene::IMeshBuffer *mb) override + { + if (!mb) + return; + drawBuffers(mb->getVertexBuffer(), mb->getIndexBuffer(), + mb->getPrimitiveCount(), mb->getPrimitiveType()); + } + + // Note: this should handle hw buffers + virtual void drawBuffers(const scene::IVertexBuffer *vb, + const scene::IIndexBuffer *ib, u32 primCount, + scene::E_PRIMITIVE_TYPE pType = scene::EPT_TRIANGLES) override; //! Draws the normals of a mesh buffer virtual void drawMeshBufferNormals(const scene::IMeshBuffer *mb, f32 length = 10.f, @@ -283,53 +293,70 @@ public: } protected: + /// Links a hardware buffer to either a vertex or index buffer struct SHWBufferLink { - SHWBufferLink(const scene::IMeshBuffer *_MeshBuffer) : - MeshBuffer(_MeshBuffer), - ChangedID_Vertex(0), ChangedID_Index(0), - Mapped_Vertex(scene::EHM_NEVER), Mapped_Index(scene::EHM_NEVER) + SHWBufferLink(const scene::IVertexBuffer *vb) : + VertexBuffer(vb), ChangedID(0), IsVertex(true) { - if (MeshBuffer) { - MeshBuffer->grab(); - MeshBuffer->setHWBuffer(reinterpret_cast(this)); + if (VertexBuffer) { + VertexBuffer->grab(); + VertexBuffer->setHWBuffer(this); + } + } + SHWBufferLink(const scene::IIndexBuffer *ib) : + IndexBuffer(ib), ChangedID(0), IsVertex(false) + { + if (IndexBuffer) { + IndexBuffer->grab(); + IndexBuffer->setHWBuffer(this); } } virtual ~SHWBufferLink() { - if (MeshBuffer) { - MeshBuffer->setHWBuffer(NULL); - MeshBuffer->drop(); + if (IsVertex && VertexBuffer) { + VertexBuffer->setHWBuffer(nullptr); + VertexBuffer->drop(); + } else if (!IsVertex && IndexBuffer) { + IndexBuffer->setHWBuffer(nullptr); + IndexBuffer->drop(); } } - const scene::IMeshBuffer *MeshBuffer; - u32 ChangedID_Vertex; - u32 ChangedID_Index; - scene::E_HARDWARE_MAPPING Mapped_Vertex; - scene::E_HARDWARE_MAPPING Mapped_Index; - std::list::iterator listPosition; + union { + const scene::IVertexBuffer *VertexBuffer; + const scene::IIndexBuffer *IndexBuffer; + }; + u32 ChangedID; + bool IsVertex; + std::list::iterator listPosition; }; - //! Gets hardware buffer link from a meshbuffer (may create or update buffer) - virtual SHWBufferLink *getBufferLink(const scene::IMeshBuffer *mb); + //! Gets hardware buffer link from a vertex buffer (may create or update buffer) + virtual SHWBufferLink *getBufferLink(const scene::IVertexBuffer *mb); + + //! Gets hardware buffer link from a index buffer (may create or update buffer) + virtual SHWBufferLink *getBufferLink(const scene::IIndexBuffer *mb); //! updates hardware buffer if needed (only some drivers can) virtual bool updateHardwareBuffer(SHWBufferLink *HWBuffer) { return false; } - //! Draw hardware buffer (only some drivers can) - virtual void drawHardwareBuffer(SHWBufferLink *HWBuffer) {} - //! Delete hardware buffer virtual void deleteHardwareBuffer(SHWBufferLink *HWBuffer); - //! Create hardware buffer from mesh (only some drivers can) - virtual SHWBufferLink *createHardwareBuffer(const scene::IMeshBuffer *mb) { return 0; } + //! Create hardware buffer from vertex buffer + virtual SHWBufferLink *createHardwareBuffer(const scene::IVertexBuffer *vb) { return 0; } + + //! Create hardware buffer from index buffer + virtual SHWBufferLink *createHardwareBuffer(const scene::IIndexBuffer *ib) { return 0; } public: //! Remove hardware buffer - void removeHardwareBuffer(const scene::IMeshBuffer *mb) override; + void removeHardwareBuffer(const scene::IVertexBuffer *vb) override; + + //! Remove hardware buffer + void removeHardwareBuffer(const scene::IIndexBuffer *ib) override; //! Remove all hardware buffers void removeAllHardwareBuffers() override; @@ -337,8 +364,11 @@ public: //! Update all hardware buffers, remove unused ones virtual void updateAllHardwareBuffers(); - //! is vbo recommended on this mesh? - virtual bool isHardwareBufferRecommend(const scene::IMeshBuffer *mb); + //! is vbo recommended? + virtual bool isHardwareBufferRecommend(const scene::IVertexBuffer *mb); + + //! is vbo recommended? + virtual bool isHardwareBufferRecommend(const scene::IIndexBuffer *mb); //! Create occlusion query. /** Use node for identification and mesh for occlusion test. */ diff --git a/irr/src/COpenGLDriver.cpp b/irr/src/COpenGLDriver.cpp index 875534a4e..837dc05d3 100644 --- a/irr/src/COpenGLDriver.cpp +++ b/irr/src/COpenGLDriver.cpp @@ -260,10 +260,10 @@ bool COpenGLDriver::updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) return false; #if defined(GL_ARB_vertex_buffer_object) - const scene::IMeshBuffer *mb = HWBuffer->MeshBuffer; - const void *vertices = mb->getVertices(); - const u32 vertexCount = mb->getVertexCount(); - const E_VERTEX_TYPE vType = mb->getVertexType(); + const auto *vb = HWBuffer->VertexBuffer; + const void *vertices = vb->getData(); + const u32 vertexCount = vb->getCount(); + const E_VERTEX_TYPE vType = vb->getType(); const u32 vertexSize = getVertexPitchFromType(vType); accountHWBufferUpload(vertexSize * vertexCount); @@ -307,26 +307,26 @@ bool COpenGLDriver::updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) // get or create buffer bool newBuffer = false; - if (!HWBuffer->vbo_verticesID) { - extGlGenBuffers(1, &HWBuffer->vbo_verticesID); - if (!HWBuffer->vbo_verticesID) + if (!HWBuffer->vbo_ID) { + extGlGenBuffers(1, &HWBuffer->vbo_ID); + if (!HWBuffer->vbo_ID) return false; newBuffer = true; - } else if (HWBuffer->vbo_verticesSize < vertexCount * vertexSize) { + } else if (HWBuffer->vbo_Size < vertexCount * vertexSize) { newBuffer = true; } - extGlBindBuffer(GL_ARRAY_BUFFER, HWBuffer->vbo_verticesID); + extGlBindBuffer(GL_ARRAY_BUFFER, HWBuffer->vbo_ID); // copy data to graphics card if (!newBuffer) extGlBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * vertexSize, vbuf); else { - HWBuffer->vbo_verticesSize = vertexCount * vertexSize; + HWBuffer->vbo_Size = vertexCount * vertexSize; - if (HWBuffer->Mapped_Vertex == scene::EHM_STATIC) + if (vb->getHardwareMappingHint() == scene::EHM_STATIC) extGlBufferData(GL_ARRAY_BUFFER, vertexCount * vertexSize, vbuf, GL_STATIC_DRAW); - else if (HWBuffer->Mapped_Vertex == scene::EHM_DYNAMIC) + else if (vb->getHardwareMappingHint() == scene::EHM_DYNAMIC) extGlBufferData(GL_ARRAY_BUFFER, vertexCount * vertexSize, vbuf, GL_DYNAMIC_DRAW); else // scene::EHM_STREAM extGlBufferData(GL_ARRAY_BUFFER, vertexCount * vertexSize, vbuf, GL_STREAM_DRAW); @@ -349,13 +349,13 @@ bool COpenGLDriver::updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) return false; #if defined(GL_ARB_vertex_buffer_object) - const scene::IMeshBuffer *mb = HWBuffer->MeshBuffer; + const auto *ib = HWBuffer->IndexBuffer; - const void *indices = mb->getIndices(); - u32 indexCount = mb->getIndexCount(); + const void *indices = ib->getData(); + u32 indexCount = ib->getCount(); GLenum indexSize; - switch (mb->getIndexType()) { + switch (ib->getType()) { case EIT_16BIT: { indexSize = sizeof(u16); break; @@ -373,26 +373,26 @@ bool COpenGLDriver::updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) // get or create buffer bool newBuffer = false; - if (!HWBuffer->vbo_indicesID) { - extGlGenBuffers(1, &HWBuffer->vbo_indicesID); - if (!HWBuffer->vbo_indicesID) + if (!HWBuffer->vbo_ID) { + extGlGenBuffers(1, &HWBuffer->vbo_ID); + if (!HWBuffer->vbo_ID) return false; newBuffer = true; - } else if (HWBuffer->vbo_indicesSize < indexCount * indexSize) { + } else if (HWBuffer->vbo_Size < indexCount * indexSize) { newBuffer = true; } - extGlBindBuffer(GL_ELEMENT_ARRAY_BUFFER, HWBuffer->vbo_indicesID); + extGlBindBuffer(GL_ELEMENT_ARRAY_BUFFER, HWBuffer->vbo_ID); // copy data to graphics card if (!newBuffer) extGlBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexCount * indexSize, indices); else { - HWBuffer->vbo_indicesSize = indexCount * indexSize; + HWBuffer->vbo_Size = indexCount * indexSize; - if (HWBuffer->Mapped_Index == scene::EHM_STATIC) + if (ib->getHardwareMappingHint() == scene::EHM_STATIC) extGlBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * indexSize, indices, GL_STATIC_DRAW); - else if (HWBuffer->Mapped_Index == scene::EHM_DYNAMIC) + else if (ib->getHardwareMappingHint() == scene::EHM_DYNAMIC) extGlBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * indexSize, indices, GL_DYNAMIC_DRAW); else // scene::EHM_STREAM extGlBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * indexSize, indices, GL_STREAM_DRAW); @@ -412,51 +412,62 @@ bool COpenGLDriver::updateHardwareBuffer(SHWBufferLink *HWBuffer) if (!HWBuffer) return false; - if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) { - if (HWBuffer->ChangedID_Vertex != HWBuffer->MeshBuffer->getChangedID_Vertex() || !((SHWBufferLink_opengl *)HWBuffer)->vbo_verticesID) { + auto *b = static_cast(HWBuffer); - HWBuffer->ChangedID_Vertex = HWBuffer->MeshBuffer->getChangedID_Vertex(); - - if (!updateVertexHardwareBuffer((SHWBufferLink_opengl *)HWBuffer)) + if (b->IsVertex) { + assert(b->VertexBuffer); + if (b->ChangedID != b->VertexBuffer->getChangedID() || !b->vbo_ID) { + if (!updateVertexHardwareBuffer(b)) return false; + b->ChangedID = b->VertexBuffer->getChangedID(); + } + } else { + assert(b->IndexBuffer); + if (b->ChangedID != b->IndexBuffer->getChangedID() || !b->vbo_ID) { + if (!updateIndexHardwareBuffer(b)) + return false; + b->ChangedID = b->IndexBuffer->getChangedID(); } } - - if (HWBuffer->Mapped_Index != scene::EHM_NEVER) { - if (HWBuffer->ChangedID_Index != HWBuffer->MeshBuffer->getChangedID_Index() || !((SHWBufferLink_opengl *)HWBuffer)->vbo_indicesID) { - - HWBuffer->ChangedID_Index = HWBuffer->MeshBuffer->getChangedID_Index(); - - if (!updateIndexHardwareBuffer((SHWBufferLink_opengl *)HWBuffer)) - return false; - } - } - return true; } //! Create hardware buffer from meshbuffer -COpenGLDriver::SHWBufferLink *COpenGLDriver::createHardwareBuffer(const scene::IMeshBuffer *mb) +COpenGLDriver::SHWBufferLink *COpenGLDriver::createHardwareBuffer(const scene::IVertexBuffer *vb) { #if defined(GL_ARB_vertex_buffer_object) - if (!mb || (mb->getHardwareMappingHint_Index() == scene::EHM_NEVER && mb->getHardwareMappingHint_Vertex() == scene::EHM_NEVER)) + if (!vb || vb->getHardwareMappingHint() == scene::EHM_NEVER) return 0; - SHWBufferLink_opengl *HWBuffer = new SHWBufferLink_opengl(mb); + SHWBufferLink_opengl *HWBuffer = new SHWBufferLink_opengl(vb); // add to map HWBuffer->listPosition = HWBufferList.insert(HWBufferList.end(), HWBuffer); - HWBuffer->ChangedID_Vertex = HWBuffer->MeshBuffer->getChangedID_Vertex(); - HWBuffer->ChangedID_Index = HWBuffer->MeshBuffer->getChangedID_Index(); - HWBuffer->Mapped_Vertex = mb->getHardwareMappingHint_Vertex(); - HWBuffer->Mapped_Index = mb->getHardwareMappingHint_Index(); - HWBuffer->vbo_verticesID = 0; - HWBuffer->vbo_indicesID = 0; - HWBuffer->vbo_verticesSize = 0; - HWBuffer->vbo_indicesSize = 0; + if (!updateVertexHardwareBuffer(HWBuffer)) { + deleteHardwareBuffer(HWBuffer); + return 0; + } - if (!updateHardwareBuffer(HWBuffer)) { + return HWBuffer; +#else + return 0; +#endif +} + +//! Create hardware buffer from meshbuffer +COpenGLDriver::SHWBufferLink *COpenGLDriver::createHardwareBuffer(const scene::IIndexBuffer *ib) +{ +#if defined(GL_ARB_vertex_buffer_object) + if (!ib || ib->getHardwareMappingHint() == scene::EHM_NEVER) + return 0; + + SHWBufferLink_opengl *HWBuffer = new SHWBufferLink_opengl(ib); + + // add to map + HWBuffer->listPosition = HWBufferList.insert(HWBufferList.end(), HWBuffer); + + if (!updateIndexHardwareBuffer(HWBuffer)) { deleteHardwareBuffer(HWBuffer); return 0; } @@ -473,51 +484,51 @@ void COpenGLDriver::deleteHardwareBuffer(SHWBufferLink *_HWBuffer) return; #if defined(GL_ARB_vertex_buffer_object) - SHWBufferLink_opengl *HWBuffer = (SHWBufferLink_opengl *)_HWBuffer; - if (HWBuffer->vbo_verticesID) { - extGlDeleteBuffers(1, &HWBuffer->vbo_verticesID); - HWBuffer->vbo_verticesID = 0; - } - if (HWBuffer->vbo_indicesID) { - extGlDeleteBuffers(1, &HWBuffer->vbo_indicesID); - HWBuffer->vbo_indicesID = 0; + auto *HWBuffer = (SHWBufferLink_opengl *)_HWBuffer; + if (HWBuffer->vbo_ID) { + extGlDeleteBuffers(1, &HWBuffer->vbo_ID); + HWBuffer->vbo_ID = 0; } #endif CNullDriver::deleteHardwareBuffer(_HWBuffer); } -//! Draw hardware buffer -void COpenGLDriver::drawHardwareBuffer(SHWBufferLink *_HWBuffer) +void COpenGLDriver::drawBuffers(const scene::IVertexBuffer *vb, + const scene::IIndexBuffer *ib, u32 PrimitiveCount, + scene::E_PRIMITIVE_TYPE PrimitiveType) { - if (!_HWBuffer) + if (!vb || !ib) return; - updateHardwareBuffer(_HWBuffer); // check if update is needed - #if defined(GL_ARB_vertex_buffer_object) - SHWBufferLink_opengl *HWBuffer = (SHWBufferLink_opengl *)_HWBuffer; + auto *hwvert = (SHWBufferLink_opengl *) getBufferLink(vb); + auto *hwidx = (SHWBufferLink_opengl *) getBufferLink(ib); + updateHardwareBuffer(hwvert); + updateHardwareBuffer(hwidx); - const scene::IMeshBuffer *mb = HWBuffer->MeshBuffer; - const void *vertices = mb->getVertices(); - const void *indexList = mb->getIndices(); - - if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) { - extGlBindBuffer(GL_ARRAY_BUFFER, HWBuffer->vbo_verticesID); + const void *vertices = vb->getData(); + if (hwvert) { + extGlBindBuffer(GL_ARRAY_BUFFER, hwvert->vbo_ID); vertices = 0; } - if (HWBuffer->Mapped_Index != scene::EHM_NEVER) { - extGlBindBuffer(GL_ELEMENT_ARRAY_BUFFER, HWBuffer->vbo_indicesID); + const void *indexList = ib->getData(); + if (hwidx) { + extGlBindBuffer(GL_ELEMENT_ARRAY_BUFFER, hwidx->vbo_ID); indexList = 0; } - drawVertexPrimitiveList(vertices, mb->getVertexCount(), indexList, mb->getPrimitiveCount(), mb->getVertexType(), mb->getPrimitiveType(), mb->getIndexType()); + drawVertexPrimitiveList(vertices, vb->getCount(), indexList, + PrimitiveCount, vb->getType(), PrimitiveType, ib->getType()); - if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) + if (hwvert) extGlBindBuffer(GL_ARRAY_BUFFER, 0); - if (HWBuffer->Mapped_Index != scene::EHM_NEVER) + if (hwidx) extGlBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#else + drawVertexPrimitiveList(vb->getData(), vb->getCount(), ib->getData(), + PrimitiveCount, vb->getType(), PrimitiveType, ib->getType()); #endif } diff --git a/irr/src/COpenGLDriver.h b/irr/src/COpenGLDriver.h index 3c0a718fc..9c4ecd3b3 100644 --- a/irr/src/COpenGLDriver.h +++ b/irr/src/COpenGLDriver.h @@ -58,27 +58,28 @@ public: struct SHWBufferLink_opengl : public SHWBufferLink { - SHWBufferLink_opengl(const scene::IMeshBuffer *_MeshBuffer) : - SHWBufferLink(_MeshBuffer), vbo_verticesID(0), vbo_indicesID(0) {} + SHWBufferLink_opengl(const scene::IVertexBuffer *vb) : SHWBufferLink(vb) {} + SHWBufferLink_opengl(const scene::IIndexBuffer *ib) : SHWBufferLink(ib) {} - GLuint vbo_verticesID; // tmp - GLuint vbo_indicesID; // tmp - - GLuint vbo_verticesSize; // tmp - GLuint vbo_indicesSize; // tmp + GLuint vbo_ID = 0; + u32 vbo_Size = 0; }; //! updates hardware buffer if needed bool updateHardwareBuffer(SHWBufferLink *HWBuffer) override; - //! Create hardware buffer from mesh - SHWBufferLink *createHardwareBuffer(const scene::IMeshBuffer *mb) override; + //! Create hardware buffer from vertex buffer + SHWBufferLink *createHardwareBuffer(const scene::IVertexBuffer *vb) override; + + //! Create hardware buffer from index buffer + SHWBufferLink *createHardwareBuffer(const scene::IIndexBuffer *ib) override; //! Delete hardware buffer (only some drivers can) void deleteHardwareBuffer(SHWBufferLink *HWBuffer) override; - //! Draw hardware buffer - void drawHardwareBuffer(SHWBufferLink *HWBuffer) override; + void drawBuffers(const scene::IVertexBuffer *vb, + const scene::IIndexBuffer *ib, u32 primCount, + scene::E_PRIMITIVE_TYPE pType = scene::EPT_TRIANGLES) override; //! Create occlusion query. /** Use node for identification and mesh for occlusion test. */ diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index f681a3de9..46aa36d5c 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -477,44 +477,36 @@ void COpenGL3DriverBase::setTransform(E_TRANSFORMATION_STATE state, const core:: Transformation3DChanged = true; } -bool COpenGL3DriverBase::updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) +bool COpenGL3DriverBase::updateHardwareBuffer(SHWBufferLink_opengl *HWBuffer, + const void *buffer, size_t bufferSize, scene::E_HARDWARE_MAPPING hint) { - if (!HWBuffer) - return false; + assert(HWBuffer); - const scene::IMeshBuffer *mb = HWBuffer->MeshBuffer; - const void *vertices = mb->getVertices(); - const u32 vertexCount = mb->getVertexCount(); - const E_VERTEX_TYPE vType = mb->getVertexType(); - const u32 vertexSize = getVertexPitchFromType(vType); - - const void *buffer = vertices; - size_t bufferSize = vertexSize * vertexCount; accountHWBufferUpload(bufferSize); // get or create buffer bool newBuffer = false; - if (!HWBuffer->vbo_verticesID) { - GL.GenBuffers(1, &HWBuffer->vbo_verticesID); - if (!HWBuffer->vbo_verticesID) + if (!HWBuffer->vbo_ID) { + GL.GenBuffers(1, &HWBuffer->vbo_ID); + if (!HWBuffer->vbo_ID) return false; newBuffer = true; - } else if (HWBuffer->vbo_verticesSize < bufferSize) { + } else if (HWBuffer->vbo_Size < bufferSize) { newBuffer = true; } - GL.BindBuffer(GL_ARRAY_BUFFER, HWBuffer->vbo_verticesID); + GL.BindBuffer(GL_ARRAY_BUFFER, HWBuffer->vbo_ID); // copy data to graphics card if (!newBuffer) GL.BufferSubData(GL_ARRAY_BUFFER, 0, bufferSize, buffer); else { - HWBuffer->vbo_verticesSize = bufferSize; + HWBuffer->vbo_Size = bufferSize; GLenum usage = GL_STATIC_DRAW; - if (HWBuffer->Mapped_Index == scene::EHM_STREAM) + if (hint == scene::EHM_STREAM) usage = GL_STREAM_DRAW; - else if (HWBuffer->Mapped_Index == scene::EHM_DYNAMIC) + else if (hint == scene::EHM_DYNAMIC) usage = GL_DYNAMIC_DRAW; GL.BufferData(GL_ARRAY_BUFFER, bufferSize, buffer, usage); } @@ -524,67 +516,47 @@ bool COpenGL3DriverBase::updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuff return (!TEST_GL_ERROR(this)); } +bool COpenGL3DriverBase::updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) +{ + if (!HWBuffer) + return false; + + assert(HWBuffer->IsVertex); + const auto *vb = HWBuffer->VertexBuffer; + assert(vb); + + const u32 vertexSize = getVertexPitchFromType(vb->getType()); + const size_t bufferSize = vertexSize * vb->getCount(); + + return updateHardwareBuffer(HWBuffer, vb->getData(), bufferSize, vb->getHardwareMappingHint()); +} + bool COpenGL3DriverBase::updateIndexHardwareBuffer(SHWBufferLink_opengl *HWBuffer) { if (!HWBuffer) return false; - const scene::IMeshBuffer *mb = HWBuffer->MeshBuffer; + assert(!HWBuffer->IsVertex); + const auto *ib = HWBuffer->IndexBuffer; + assert(ib); - const void *indices = mb->getIndices(); - u32 indexCount = mb->getIndexCount(); - - GLenum indexSize; - switch (mb->getIndexType()) { - case (EIT_16BIT): { + u32 indexSize; + switch (ib->getType()) { + case EIT_16BIT: indexSize = sizeof(u16); break; - } - case (EIT_32BIT): { + case EIT_32BIT: indexSize = sizeof(u32); break; - } - default: { + default: return false; } - } - const size_t bufferSize = indexCount * indexSize; - accountHWBufferUpload(bufferSize); + const size_t bufferSize = ib->getCount() * indexSize; - // get or create buffer - bool newBuffer = false; - if (!HWBuffer->vbo_indicesID) { - GL.GenBuffers(1, &HWBuffer->vbo_indicesID); - if (!HWBuffer->vbo_indicesID) - return false; - newBuffer = true; - } else if (HWBuffer->vbo_indicesSize < bufferSize) { - newBuffer = true; - } - - GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, HWBuffer->vbo_indicesID); - - // copy data to graphics card - if (!newBuffer) - GL.BufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, bufferSize, indices); - else { - HWBuffer->vbo_indicesSize = bufferSize; - - GLenum usage = GL_STATIC_DRAW; - if (HWBuffer->Mapped_Index == scene::EHM_STREAM) - usage = GL_STREAM_DRAW; - else if (HWBuffer->Mapped_Index == scene::EHM_DYNAMIC) - usage = GL_DYNAMIC_DRAW; - GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, bufferSize, indices, usage); - } - - GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - return (!TEST_GL_ERROR(this)); + return updateHardwareBuffer(HWBuffer, ib->getData(), bufferSize, ib->getHardwareMappingHint()); } -//! updates hardware buffer if needed bool COpenGL3DriverBase::updateHardwareBuffer(SHWBufferLink *HWBuffer) { if (!HWBuffer) @@ -592,50 +564,35 @@ bool COpenGL3DriverBase::updateHardwareBuffer(SHWBufferLink *HWBuffer) auto *b = static_cast(HWBuffer); - if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) { - if (HWBuffer->ChangedID_Vertex != HWBuffer->MeshBuffer->getChangedID_Vertex() || !b->vbo_verticesID) { - - HWBuffer->ChangedID_Vertex = HWBuffer->MeshBuffer->getChangedID_Vertex(); - + if (b->IsVertex) { + assert(b->VertexBuffer); + if (b->ChangedID != b->VertexBuffer->getChangedID() || !b->vbo_ID) { if (!updateVertexHardwareBuffer(b)) return false; + b->ChangedID = b->VertexBuffer->getChangedID(); } - } - - if (HWBuffer->Mapped_Index != scene::EHM_NEVER) { - if (HWBuffer->ChangedID_Index != HWBuffer->MeshBuffer->getChangedID_Index() || !b->vbo_indicesID) { - - HWBuffer->ChangedID_Index = HWBuffer->MeshBuffer->getChangedID_Index(); - + } else { + assert(b->IndexBuffer); + if (b->ChangedID != b->IndexBuffer->getChangedID() || !b->vbo_ID) { if (!updateIndexHardwareBuffer(b)) return false; + b->ChangedID = b->IndexBuffer->getChangedID(); } } - return true; } -//! Create hardware buffer from meshbuffer -COpenGL3DriverBase::SHWBufferLink *COpenGL3DriverBase::createHardwareBuffer(const scene::IMeshBuffer *mb) +COpenGL3DriverBase::SHWBufferLink *COpenGL3DriverBase::createHardwareBuffer(const scene::IVertexBuffer *vb) { - if (!mb || (mb->getHardwareMappingHint_Index() == scene::EHM_NEVER && mb->getHardwareMappingHint_Vertex() == scene::EHM_NEVER)) + if (!vb || vb->getHardwareMappingHint() == scene::EHM_NEVER) return 0; - SHWBufferLink_opengl *HWBuffer = new SHWBufferLink_opengl(mb); + SHWBufferLink_opengl *HWBuffer = new SHWBufferLink_opengl(vb); // add to map HWBuffer->listPosition = HWBufferList.insert(HWBufferList.end(), HWBuffer); - HWBuffer->ChangedID_Vertex = HWBuffer->MeshBuffer->getChangedID_Vertex(); - HWBuffer->ChangedID_Index = HWBuffer->MeshBuffer->getChangedID_Index(); - HWBuffer->Mapped_Vertex = mb->getHardwareMappingHint_Vertex(); - HWBuffer->Mapped_Index = mb->getHardwareMappingHint_Index(); - HWBuffer->vbo_verticesID = 0; - HWBuffer->vbo_indicesID = 0; - HWBuffer->vbo_verticesSize = 0; - HWBuffer->vbo_indicesSize = 0; - - if (!updateHardwareBuffer(HWBuffer)) { + if (!updateVertexHardwareBuffer(HWBuffer)) { deleteHardwareBuffer(HWBuffer); return 0; } @@ -643,57 +600,70 @@ COpenGL3DriverBase::SHWBufferLink *COpenGL3DriverBase::createHardwareBuffer(cons return HWBuffer; } -void COpenGL3DriverBase::deleteHardwareBuffer(SHWBufferLink *_HWBuffer) +COpenGL3DriverBase::SHWBufferLink *COpenGL3DriverBase::createHardwareBuffer(const scene::IIndexBuffer *ib) { - if (!_HWBuffer) - return; + if (!ib || ib->getHardwareMappingHint() == scene::EHM_NEVER) + return 0; - SHWBufferLink_opengl *HWBuffer = static_cast(_HWBuffer); - if (HWBuffer->vbo_verticesID) { - GL.DeleteBuffers(1, &HWBuffer->vbo_verticesID); - HWBuffer->vbo_verticesID = 0; - } - if (HWBuffer->vbo_indicesID) { - GL.DeleteBuffers(1, &HWBuffer->vbo_indicesID); - HWBuffer->vbo_indicesID = 0; + SHWBufferLink_opengl *HWBuffer = new SHWBufferLink_opengl(ib); + + // add to map + HWBuffer->listPosition = HWBufferList.insert(HWBufferList.end(), HWBuffer); + + if (!updateIndexHardwareBuffer(HWBuffer)) { + deleteHardwareBuffer(HWBuffer); + return 0; } - CNullDriver::deleteHardwareBuffer(_HWBuffer); + return HWBuffer; } -//! Draw hardware buffer -void COpenGL3DriverBase::drawHardwareBuffer(SHWBufferLink *_HWBuffer) +void COpenGL3DriverBase::deleteHardwareBuffer(SHWBufferLink *HWBuffer) { - if (!_HWBuffer) + if (!HWBuffer) return; - SHWBufferLink_opengl *HWBuffer = static_cast(_HWBuffer); - - updateHardwareBuffer(HWBuffer); // check if update is needed - - const scene::IMeshBuffer *mb = HWBuffer->MeshBuffer; - const void *vertices = mb->getVertices(); - const void *indexList = mb->getIndices(); - - if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) { - GL.BindBuffer(GL_ARRAY_BUFFER, HWBuffer->vbo_verticesID); - vertices = 0; + auto *b = static_cast(HWBuffer); + if (b->vbo_ID) { + GL.DeleteBuffers(1, &b->vbo_ID); + b->vbo_ID = 0; } - if (HWBuffer->Mapped_Index != scene::EHM_NEVER) { - GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, HWBuffer->vbo_indicesID); - indexList = 0; + CNullDriver::deleteHardwareBuffer(HWBuffer); +} + +void COpenGL3DriverBase::drawBuffers(const scene::IVertexBuffer *vb, + const scene::IIndexBuffer *ib, u32 PrimitiveCount, + scene::E_PRIMITIVE_TYPE PrimitiveType) +{ + if (!vb || !ib) + return; + + auto *hwvert = static_cast(getBufferLink(vb)); + auto *hwidx = static_cast(getBufferLink(ib)); + updateHardwareBuffer(hwvert); + updateHardwareBuffer(hwidx); + + const void *vertices = vb->getData(); + if (hwvert) { + assert(hwvert->IsVertex); + GL.BindBuffer(GL_ARRAY_BUFFER, hwvert->vbo_ID); + vertices = nullptr; } - drawVertexPrimitiveList(vertices, mb->getVertexCount(), - indexList, mb->getPrimitiveCount(), - mb->getVertexType(), mb->getPrimitiveType(), - mb->getIndexType()); + const void *indexList = ib->getData(); + if (hwidx) { + assert(!hwidx->IsVertex); + GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, hwidx->vbo_ID); + indexList = nullptr; + } - if (HWBuffer->Mapped_Vertex != scene::EHM_NEVER) + drawVertexPrimitiveList(vertices, vb->getCount(), indexList, + PrimitiveCount, vb->getType(), PrimitiveType, ib->getType()); + + if (hwvert) GL.BindBuffer(GL_ARRAY_BUFFER, 0); - - if (HWBuffer->Mapped_Index != scene::EHM_NEVER) + if (hwidx) GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } diff --git a/irr/src/OpenGL/Driver.h b/irr/src/OpenGL/Driver.h index be4e4db9e..3104b164d 100644 --- a/irr/src/OpenGL/Driver.h +++ b/irr/src/OpenGL/Driver.h @@ -46,16 +46,11 @@ public: struct SHWBufferLink_opengl : public SHWBufferLink { - SHWBufferLink_opengl(const scene::IMeshBuffer *meshBuffer) : - SHWBufferLink(meshBuffer), vbo_verticesID(0), vbo_indicesID(0), vbo_verticesSize(0), vbo_indicesSize(0) - { - } + SHWBufferLink_opengl(const scene::IVertexBuffer *vb) : SHWBufferLink(vb) {} + SHWBufferLink_opengl(const scene::IIndexBuffer *ib) : SHWBufferLink(ib) {} - u32 vbo_verticesID; // tmp - u32 vbo_indicesID; // tmp - - u32 vbo_verticesSize; // tmp - u32 vbo_indicesSize; // tmp + GLuint vbo_ID = 0; + u32 vbo_Size = 0; }; bool updateVertexHardwareBuffer(SHWBufferLink_opengl *HWBuffer); @@ -64,14 +59,18 @@ public: //! updates hardware buffer if needed bool updateHardwareBuffer(SHWBufferLink *HWBuffer) override; - //! Create hardware buffer from mesh - SHWBufferLink *createHardwareBuffer(const scene::IMeshBuffer *mb) override; + //! Create hardware buffer from vertex buffer + SHWBufferLink *createHardwareBuffer(const scene::IVertexBuffer *vb) override; + + //! Create hardware buffer from index buffer + SHWBufferLink *createHardwareBuffer(const scene::IIndexBuffer *ib) override; //! Delete hardware buffer (only some drivers can) void deleteHardwareBuffer(SHWBufferLink *HWBuffer) override; - //! Draw hardware buffer - void drawHardwareBuffer(SHWBufferLink *HWBuffer) override; + void drawBuffers(const scene::IVertexBuffer *vb, + const scene::IIndexBuffer *ib, u32 primCount, + scene::E_PRIMITIVE_TYPE pType = scene::EPT_TRIANGLES) override; IRenderTarget *addRenderTarget() override; @@ -291,10 +290,7 @@ protected: LockRenderStateMode = false; } - void draw2D3DVertexPrimitiveList(const void *vertices, - u32 vertexCount, const void *indexList, u32 primitiveCount, - E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, - E_INDEX_TYPE iType, bool is3D); + bool updateHardwareBuffer(SHWBufferLink_opengl *b, const void *buffer, size_t bufferSize, scene::E_HARDWARE_MAPPING hint); void createMaterialRenderers(); From 62131fe2954d5e79651f9e47611675ba43da4d31 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 28 Aug 2024 21:46:52 +0200 Subject: [PATCH 071/200] Put all pieces together and clean up leftover code --- irr/include/CIndexBuffer.h | 2 +- irr/include/CMeshBuffer.h | 26 +---------- irr/include/IIndexBuffer.h | 26 +++++++++++ irr/include/IMeshBuffer.h | 55 ++--------------------- irr/include/SSkinMeshBuffer.h | 82 +++++++++++++--------------------- irr/src/CMeshManipulator.cpp | 35 +++++++++------ irr/src/COBJMeshFileLoader.cpp | 7 +-- src/client/hud.cpp | 17 +++---- src/client/hud.h | 3 +- src/client/mapblock_mesh.cpp | 14 ++---- src/client/mapblock_mesh.h | 7 ++- 11 files changed, 105 insertions(+), 169 deletions(-) diff --git a/irr/include/CIndexBuffer.h b/irr/include/CIndexBuffer.h index 4701c2e34..676408e65 100644 --- a/irr/include/CIndexBuffer.h +++ b/irr/include/CIndexBuffer.h @@ -24,7 +24,7 @@ public: #endif } - video::E_INDEX_TYPE getType() const + video::E_INDEX_TYPE getType() const override { static_assert(sizeof(T) == 2 || sizeof(T) == 4, "invalid index type"); return sizeof(T) == 2 ? video::EIT_16BIT : video::EIT_32BIT; diff --git a/irr/include/CMeshBuffer.h b/irr/include/CMeshBuffer.h index 6b13a8982..6eb325889 100644 --- a/irr/include/CMeshBuffer.h +++ b/irr/include/CMeshBuffer.h @@ -20,7 +20,7 @@ class CMeshBuffer : public IMeshBuffer public: //! Default constructor for empty meshbuffer CMeshBuffer() : - HWBuffer(NULL), PrimitiveType(EPT_TRIANGLES) + PrimitiveType(EPT_TRIANGLES) { #ifdef _DEBUG setDebugName("CMeshBuffer"); @@ -69,18 +69,6 @@ public: return Indices; } - // TEMPORARY helper for direct buffer acess - inline auto &VertexBuffer() - { - return Vertices->Data; - } - - // TEMPORARY helper for direct buffer acess - inline auto &IndexBuffer() - { - return Indices->Data; - } - //! Get the axis aligned bounding box /** \return Axis aligned bounding box of this buffer. */ const core::aabbox3d &getBoundingBox() const override @@ -142,18 +130,6 @@ public: return PrimitiveType; } - void setHWBuffer(void *ptr) const override - { - HWBuffer = ptr; - } - - void *getHWBuffer() const override - { - return HWBuffer; - } - - mutable void *HWBuffer; - //! Material for this meshbuffer. video::SMaterial Material; //! Vertex buffer diff --git a/irr/include/IIndexBuffer.h b/irr/include/IIndexBuffer.h index bdbbb6dcc..01282f0c8 100644 --- a/irr/include/IIndexBuffer.h +++ b/irr/include/IIndexBuffer.h @@ -7,6 +7,7 @@ #include "IReferenceCounted.h" #include "irrArray.h" #include "EHardwareBufferFlags.h" +#include "EPrimitiveTypes.h" #include "SVertexIndex.h" namespace irr @@ -50,6 +51,31 @@ public: //! Used by the VideoDriver to remember the buffer link. virtual void setHWBuffer(void *ptr) const = 0; virtual void *getHWBuffer() const = 0; + + //! Calculate how many geometric primitives would be drawn + u32 getPrimitiveCount(E_PRIMITIVE_TYPE primitiveType) const + { + const u32 indexCount = getCount(); + switch (primitiveType) { + case scene::EPT_POINTS: + return indexCount; + case scene::EPT_LINE_STRIP: + return indexCount - 1; + case scene::EPT_LINE_LOOP: + return indexCount; + case scene::EPT_LINES: + return indexCount / 2; + case scene::EPT_TRIANGLE_STRIP: + return (indexCount - 2); + case scene::EPT_TRIANGLE_FAN: + return (indexCount - 2); + case scene::EPT_TRIANGLES: + return indexCount / 3; + case scene::EPT_POINT_SPRITES: + return indexCount; + } + return 0; + } }; } // end namespace scene diff --git a/irr/include/IMeshBuffer.h b/irr/include/IMeshBuffer.h index 8a43db87e..55c05211a 100644 --- a/irr/include/IMeshBuffer.h +++ b/irr/include/IMeshBuffer.h @@ -176,18 +176,6 @@ public: return getVertexBuffer()->getTCoords(i); } - //! get the current hardware mapping hint - inline E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const - { - return getVertexBuffer()->getHardwareMappingHint(); - } - - //! get the current hardware mapping hint - inline E_HARDWARE_MAPPING getHardwareMappingHint_Index() const - { - return getIndexBuffer()->getHardwareMappingHint(); - } - //! set the hardware mapping hint, for driver inline void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) { @@ -206,26 +194,8 @@ public: getIndexBuffer()->setDirty(); } - //! Get the currently used ID for identification of changes. - /** This shouldn't be used for anything outside the VideoDriver. */ - inline u32 getChangedID_Vertex() const - { - return getVertexBuffer()->getChangedID(); - } - - //! Get the currently used ID for identification of changes. - /** This shouldn't be used for anything outside the VideoDriver. */ - inline u32 getChangedID_Index() const - { - return getIndexBuffer()->getChangedID(); - } - /* End helpers */ - //! Used by the VideoDriver to remember the buffer link. - virtual void setHWBuffer(void *ptr) const = 0; - virtual void *getHWBuffer() const = 0; - //! Describe what kind of primitive geometry is used by the meshbuffer /** Note: Default is EPT_TRIANGLES. Using other types is fine for rendering. But meshbuffer manipulation functions might expect type EPT_TRIANGLES @@ -237,32 +207,13 @@ public: virtual E_PRIMITIVE_TYPE getPrimitiveType() const = 0; //! Calculate how many geometric primitives are used by this meshbuffer - virtual u32 getPrimitiveCount() const + u32 getPrimitiveCount() const { - const u32 indexCount = getIndexCount(); - switch (getPrimitiveType()) { - case scene::EPT_POINTS: - return indexCount; - case scene::EPT_LINE_STRIP: - return indexCount - 1; - case scene::EPT_LINE_LOOP: - return indexCount; - case scene::EPT_LINES: - return indexCount / 2; - case scene::EPT_TRIANGLE_STRIP: - return (indexCount - 2); - case scene::EPT_TRIANGLE_FAN: - return (indexCount - 2); - case scene::EPT_TRIANGLES: - return indexCount / 3; - case scene::EPT_POINT_SPRITES: - return indexCount; - } - return 0; + return getIndexBuffer()->getPrimitiveCount(getPrimitiveType()); } //! Calculate size of vertices and indices in memory - virtual size_t getSize() const + size_t getSize() const { size_t ret = 0; switch (getVertexType()) { diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index 949551161..fa6eae63a 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -21,7 +21,7 @@ struct SSkinMeshBuffer : public IMeshBuffer //! Default constructor SSkinMeshBuffer(video::E_VERTEX_TYPE vt = video::EVT_STANDARD) : VertexType(vt), PrimitiveType(EPT_TRIANGLES), - HWBuffer(nullptr), BoundingBoxNeedsRecalculated(true) + BoundingBoxNeedsRecalculated(true) { #ifdef _DEBUG setDebugName("SSkinMeshBuffer"); @@ -72,7 +72,7 @@ struct SSkinMeshBuffer : public IMeshBuffer } } - scene::IVertexBuffer *getVertexBuffer() + scene::IVertexBuffer *getVertexBuffer() override { switch (VertexType) { case video::EVT_2TCOORDS: @@ -119,6 +119,28 @@ struct SSkinMeshBuffer : public IMeshBuffer BoundingBox = box; } +private: + template void recalculateBoundingBox(const CVertexBuffer *buf) + { + if (!buf->getCount()) { + BoundingBox.reset(0, 0, 0); + } else { + auto &vertices = buf->Data; + BoundingBox.reset(vertices[0].Pos); + for (size_t i = 1; i < vertices.size(); ++i) + BoundingBox.addInternalPoint(vertices[i].Pos); + } + } + + template static void copyVertex(const T1 &src, T2 &dst) + { + dst.Pos = src.Pos; + dst.Normal = src.Normal; + dst.Color = src.Color; + dst.TCoords = src.TCoords; + } +public: + //! Recalculate bounding box void recalculateBoundingBox() override { @@ -129,36 +151,15 @@ struct SSkinMeshBuffer : public IMeshBuffer switch (VertexType) { case video::EVT_STANDARD: { - if (!Vertices_Standard->getCount()) - BoundingBox.reset(0, 0, 0); - else { - auto &vertices = Vertices_Standard->Data; - BoundingBox.reset(vertices[0].Pos); - for (size_t i = 1; i < vertices.size(); ++i) - BoundingBox.addInternalPoint(vertices[i].Pos); - } + recalculateBoundingBox(Vertices_Standard); break; } case video::EVT_2TCOORDS: { - if (!Vertices_2TCoords->getCount()) - BoundingBox.reset(0, 0, 0); - else { - auto &vertices = Vertices_2TCoords->Data; - BoundingBox.reset(vertices[0].Pos); - for (size_t i = 1; i < vertices.size(); ++i) - BoundingBox.addInternalPoint(vertices[i].Pos); - } + recalculateBoundingBox(Vertices_2TCoords); break; } case video::EVT_TANGENTS: { - if (!Vertices_Tangents->getCount()) - BoundingBox.reset(0, 0, 0); - else { - auto &vertices = Vertices_Tangents->Data; - BoundingBox.reset(vertices[0].Pos); - for (size_t i = 1; i < vertices.size(); ++i) - BoundingBox.addInternalPoint(vertices[i].Pos); - } + recalculateBoundingBox(Vertices_Tangents); break; } } @@ -170,10 +171,7 @@ struct SSkinMeshBuffer : public IMeshBuffer if (VertexType == video::EVT_STANDARD) { video::S3DVertex2TCoords Vertex; for (const auto &Vertex_Standard : Vertices_Standard->Data) { - Vertex.Color = Vertex_Standard.Color; - Vertex.Pos = Vertex_Standard.Pos; - Vertex.Normal = Vertex_Standard.Normal; - Vertex.TCoords = Vertex_Standard.TCoords; + copyVertex(Vertex_Standard, Vertex); Vertices_2TCoords->Data.push_back(Vertex); } Vertices_Standard->Data.clear(); @@ -185,12 +183,9 @@ struct SSkinMeshBuffer : public IMeshBuffer void convertToTangents() { if (VertexType == video::EVT_STANDARD) { - video::S3DVertexTangents Vertex; + video::S3DVertexTangents Vertex; for (const auto &Vertex_Standard : Vertices_Standard->Data) { - Vertex.Color = Vertex_Standard.Color; - Vertex.Pos = Vertex_Standard.Pos; - Vertex.Normal = Vertex_Standard.Normal; - Vertex.TCoords = Vertex_Standard.TCoords; + copyVertex(Vertex_Standard, Vertex); Vertices_Tangents->Data.push_back(Vertex); } Vertices_Standard->Data.clear(); @@ -198,10 +193,7 @@ struct SSkinMeshBuffer : public IMeshBuffer } else if (VertexType == video::EVT_2TCOORDS) { video::S3DVertexTangents Vertex; for (const auto &Vertex_2TCoords : Vertices_2TCoords->Data) { - Vertex.Color = Vertex_2TCoords.Color; - Vertex.Pos = Vertex_2TCoords.Pos; - Vertex.Normal = Vertex_2TCoords.Normal; - Vertex.TCoords = Vertex_2TCoords.TCoords; + copyVertex(Vertex_2TCoords, Vertex); Vertices_Tangents->Data.push_back(Vertex); } Vertices_2TCoords->Data.clear(); @@ -227,16 +219,6 @@ struct SSkinMeshBuffer : public IMeshBuffer return PrimitiveType; } - void setHWBuffer(void *ptr) const override - { - HWBuffer = ptr; - } - - void *getHWBuffer() const override - { - return HWBuffer; - } - //! Call this after changing the positions of any vertex. void boundingBoxNeedsRecalculated(void) { BoundingBoxNeedsRecalculated = true; } @@ -255,8 +237,6 @@ struct SSkinMeshBuffer : public IMeshBuffer //! Primitive type used for rendering (triangles, lines, ...) E_PRIMITIVE_TYPE PrimitiveType; - mutable void *HWBuffer; - bool BoundingBoxNeedsRecalculated; }; diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index 9b2bd0cde..2c9d05336 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -67,7 +67,7 @@ void recalculateNormalsT(IMeshBuffer *buffer, bool smooth, bool angleWeighted) core::vector3df weight(1.f, 1.f, 1.f); if (angleWeighted) - weight = irr::scene::getAngleWeight(v1, v2, v3); // writing irr::scene:: necessary for borland + weight = getAngleWeight(v1, v2, v3); buffer->getNormal(idx[i + 0]) += weight.X * normal; buffer->getNormal(idx[i + 1]) += weight.Y * normal; @@ -115,6 +115,21 @@ void CMeshManipulator::recalculateNormals(scene::IMesh *mesh, bool smooth, bool } } +template +void copyVertices(const scene::IVertexBuffer *src, scene::CVertexBuffer *dst) +{ + _IRR_DEBUG_BREAK_IF(T::getType() != src->getType()); + auto *data = static_cast(src->getData()); + dst->Data.assign(data, data + src->getCount()); +} + +static void copyIndices(const scene::IIndexBuffer *src, scene::SIndexBuffer *dst) +{ + _IRR_DEBUG_BREAK_IF(src->getType() != video::EIT_16BIT); + auto *data = static_cast(src->getData()); + dst->Data.assign(data, data + src->getCount()); +} + //! Clones a static IMesh into a modifyable SMesh. // not yet 32bit SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const @@ -132,30 +147,24 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const case video::EVT_STANDARD: { SMeshBuffer *buffer = new SMeshBuffer(); buffer->Material = mb->getMaterial(); - auto *vt = static_cast(mb->getVertices()); - buffer->VertexBuffer().insert(buffer->VertexBuffer().end(), vt, vt + mb->getVertexCount()); - auto *indices = mb->getIndices(); - buffer->IndexBuffer().insert(buffer->IndexBuffer().end(), indices, indices + mb->getIndexCount()); + copyVertices(mb->getVertexBuffer(), buffer->Vertices); + copyIndices(mb->getIndexBuffer(), buffer->Indices); clone->addMeshBuffer(buffer); buffer->drop(); } break; case video::EVT_2TCOORDS: { SMeshBufferLightMap *buffer = new SMeshBufferLightMap(); buffer->Material = mb->getMaterial(); - auto *vt = static_cast(mb->getVertices()); - buffer->VertexBuffer().insert(buffer->VertexBuffer().end(), vt, vt + mb->getVertexCount()); - auto *indices = mb->getIndices(); - buffer->IndexBuffer().insert(buffer->IndexBuffer().end(), indices, indices + mb->getIndexCount()); + copyVertices(mb->getVertexBuffer(), buffer->Vertices); + copyIndices(mb->getIndexBuffer(), buffer->Indices); clone->addMeshBuffer(buffer); buffer->drop(); } break; case video::EVT_TANGENTS: { SMeshBufferTangents *buffer = new SMeshBufferTangents(); buffer->Material = mb->getMaterial(); - auto *vt = static_cast(mb->getVertices()); - buffer->VertexBuffer().insert(buffer->VertexBuffer().end(), vt, vt + mb->getVertexCount()); - auto *indices = mb->getIndices(); - buffer->IndexBuffer().insert(buffer->IndexBuffer().end(), indices, indices + mb->getIndexCount()); + copyVertices(mb->getVertexBuffer(), buffer->Vertices); + copyIndices(mb->getIndexBuffer(), buffer->Indices); clone->addMeshBuffer(buffer); buffer->drop(); } break; diff --git a/irr/src/COBJMeshFileLoader.cpp b/irr/src/COBJMeshFileLoader.cpp index 064fc4186..bceba6a90 100644 --- a/irr/src/COBJMeshFileLoader.cpp +++ b/irr/src/COBJMeshFileLoader.cpp @@ -192,6 +192,7 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file) faceCorners.set_used(0); // fast clear // read in all vertices + auto &Vertices = currMtl->Meshbuffer->Vertices->Data; linePtr = goNextWord(linePtr, endPtr); while (0 != linePtr[0]) { // Array to communicate with retrieveVertexIndices() @@ -228,8 +229,8 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file) if (n != currMtl->VertMap.end()) { vertLocation = n->second; } else { - currMtl->Meshbuffer->VertexBuffer().push_back(v); - vertLocation = currMtl->Meshbuffer->VertexBuffer().size() - 1; + Vertices.push_back(v); + vertLocation = Vertices.size() - 1; currMtl->VertMap.emplace(v, vertLocation); } @@ -247,7 +248,7 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file) } // triangulate the face - auto &Indices = currMtl->Meshbuffer->IndexBuffer(); + auto &Indices = currMtl->Meshbuffer->Indices->Data; const int c = faceCorners[0]; for (u32 i = 1; i < faceCorners.size() - 1; ++i) { // Add a triangle diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 52fbfb6b4..3ff83bdae 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -133,9 +133,10 @@ Hud::Hud(Client *client, LocalPlayer *player, rangelim(g_settings->getS16("selectionbox_width"), 1, 5); // Prepare mesh for compass drawing - auto &b = m_rotation_mesh_buffer; - auto &vertices = b.Vertices->Data; - auto &indices = b.Indices->Data; + m_rotation_mesh_buffer.reset(new scene::SMeshBuffer()); + auto *b = m_rotation_mesh_buffer.get(); + auto &vertices = b->Vertices->Data; + auto &indices = b->Indices->Data; vertices.resize(4); indices.resize(6); @@ -154,9 +155,9 @@ Hud::Hud(Client *client, LocalPlayer *player, indices[4] = 3; indices[5] = 0; - b.getMaterial().Lighting = false; - b.getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - //b.setHardwareMappingHint(scene::EHM_STATIC); // FIXME: incorrectly stack allocated, not safe! + b->getMaterial().Lighting = false; + b->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + b->setHardwareMappingHint(scene::EHM_STATIC); } void Hud::readScalingSetting() @@ -658,10 +659,10 @@ void Hud::drawCompassRotate(HudElement *e, video::ITexture *texture, driver->setTransform(video::ETS_VIEW, core::matrix4()); driver->setTransform(video::ETS_WORLD, Matrix); - video::SMaterial &material = m_rotation_mesh_buffer.getMaterial(); + auto &material = m_rotation_mesh_buffer->getMaterial(); material.TextureLayers[0].Texture = texture; driver->setMaterial(material); - driver->drawMeshBuffer(&m_rotation_mesh_buffer); + driver->drawMeshBuffer(m_rotation_mesh_buffer.get()); driver->setTransform(video::ETS_WORLD, core::matrix4()); driver->setTransform(video::ETS_VIEW, oldViewMat); diff --git a/src/client/hud.h b/src/client/hud.h index 55b24abd3..120215f45 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "irr_ptr.h" #include "irr_aabb3d.h" #include "../hud.h" @@ -152,7 +153,7 @@ private: video::SMaterial m_selection_material; video::SMaterial m_block_bounds_material; - scene::SMeshBuffer m_rotation_mesh_buffer; + irr_ptr m_rotation_mesh_buffer; enum { diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 59883d052..5b47a32ff 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -594,12 +594,9 @@ void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector &output void PartialMeshBuffer::draw(video::IVideoDriver *driver) const { - // Swap out the index buffer before drawing the mesh - auto *old = m_buffer->Indices; - m_buffer->Indices = m_indices.get(); - m_buffer->setDirty(scene::EBT_INDEX); // TODO remove - driver->drawMeshBuffer(m_buffer); - m_buffer->Indices = old; + const auto pType = m_buffer->getPrimitiveType(); + driver->drawBuffers(m_buffer->getVertexBuffer(), m_indices.get(), + m_indices->getPrimitiveCount(pType), pType); } /* @@ -785,11 +782,6 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs } } - // Transparent parts have changing indices - // TODO: remove - for (auto &it : m_transparent_triangles) - it.buffer->setHardwareMappingHint(scene::EHM_STREAM, scene::EBT_INDEX); - m_bsp_tree.buildTree(&m_transparent_triangles, data->side_length); // Check if animation is required for this mesh diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index e958814f3..d2c651525 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -145,19 +145,18 @@ private: * * Attach alternate `Indices` to an existing mesh buffer, to make it possible to use different * indices with the same vertex buffer. - * */ class PartialMeshBuffer { public: PartialMeshBuffer(scene::SMeshBuffer *buffer, std::vector &&vertex_indices) : - m_buffer(buffer) + m_buffer(buffer), m_indices(make_irr()) { - m_indices.reset(new scene::SIndexBuffer()); m_indices->Data = std::move(vertex_indices); + m_indices->setHardwareMappingHint(scene::EHM_STATIC); } - scene::IMeshBuffer *getBuffer() const { return m_buffer; } + auto *getBuffer() const { return m_buffer; } void draw(video::IVideoDriver *driver) const; From 3fb404961240eed6a3d97eaa0dcdda71241d098f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 29 Aug 2024 16:13:30 +0200 Subject: [PATCH 072/200] Prevent accidentally copy/move of refcounted objects --- irr/include/IReferenceCounted.h | 4 ++++ src/util/pointer.h | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/irr/include/IReferenceCounted.h b/irr/include/IReferenceCounted.h index 6f85bb904..e1000d389 100644 --- a/irr/include/IReferenceCounted.h +++ b/irr/include/IReferenceCounted.h @@ -51,6 +51,10 @@ public: { } + // Reference counted objects can be neither copied nor moved. + IReferenceCounted(const IReferenceCounted &) = delete; + IReferenceCounted &operator=(const IReferenceCounted &) = delete; + //! Grabs the object. Increments the reference counter by one. /** Someone who calls grab() to an object, should later also call drop() to it. If an object never gets as much drop() as diff --git a/src/util/pointer.h b/src/util/pointer.h index 528897a1c..b7fad4fe1 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -273,9 +273,7 @@ public: void grab() noexcept { ++m_refcount; } void drop() noexcept { if (--m_refcount == 0) delete this; } - // Preserve own reference count. - IntrusiveReferenceCounted(const IntrusiveReferenceCounted &) {} - IntrusiveReferenceCounted &operator=(const IntrusiveReferenceCounted &) { return *this; } + DISABLE_CLASS_COPY(IntrusiveReferenceCounted) private: u32 m_refcount = 1; }; From e55fb6da71ff311cd6aba4314304f78053a62db1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 1 Sep 2024 15:17:54 +0200 Subject: [PATCH 073/200] Mark a bunch of classes as final --- irr/include/CIndexBuffer.h | 2 +- irr/include/CMeshBuffer.h | 2 +- irr/include/CVertexBuffer.h | 2 +- irr/include/SAnimatedMesh.h | 2 +- irr/include/SMesh.h | 2 +- irr/include/SSkinMeshBuffer.h | 2 +- irr/src/CFileSystem.h | 2 +- irr/src/CImage.h | 2 +- irr/src/CLimitReadFile.h | 2 +- irr/src/CMemoryFile.h | 2 +- irr/src/CReadFile.h | 2 +- irr/src/CSceneManager.h | 2 +- src/client/inputhandler.h | 7 ++++--- src/client/texturesource.cpp | 2 +- src/network/mtp/impl.h | 2 +- 15 files changed, 18 insertions(+), 17 deletions(-) diff --git a/irr/include/CIndexBuffer.h b/irr/include/CIndexBuffer.h index 676408e65..4318ddaab 100644 --- a/irr/include/CIndexBuffer.h +++ b/irr/include/CIndexBuffer.h @@ -13,7 +13,7 @@ namespace scene { //! Template implementation of the IIndexBuffer interface template -class CIndexBuffer : public IIndexBuffer +class CIndexBuffer final : public IIndexBuffer { public: //! Default constructor for empty buffer diff --git a/irr/include/CMeshBuffer.h b/irr/include/CMeshBuffer.h index 6eb325889..9a6d4426f 100644 --- a/irr/include/CMeshBuffer.h +++ b/irr/include/CMeshBuffer.h @@ -15,7 +15,7 @@ namespace scene { //! Template implementation of the IMeshBuffer interface template -class CMeshBuffer : public IMeshBuffer +class CMeshBuffer final : public IMeshBuffer { public: //! Default constructor for empty meshbuffer diff --git a/irr/include/CVertexBuffer.h b/irr/include/CVertexBuffer.h index 6861913bc..3559bbddb 100644 --- a/irr/include/CVertexBuffer.h +++ b/irr/include/CVertexBuffer.h @@ -13,7 +13,7 @@ namespace scene { //! Template implementation of the IVertexBuffer interface template -class CVertexBuffer : public IVertexBuffer +class CVertexBuffer final : public IVertexBuffer { public: //! Default constructor for empty buffer diff --git a/irr/include/SAnimatedMesh.h b/irr/include/SAnimatedMesh.h index 42ba6b952..8fe14b41f 100644 --- a/irr/include/SAnimatedMesh.h +++ b/irr/include/SAnimatedMesh.h @@ -15,7 +15,7 @@ namespace scene { //! Simple implementation of the IAnimatedMesh interface. -struct SAnimatedMesh : public IAnimatedMesh +struct SAnimatedMesh final : public IAnimatedMesh { //! constructor SAnimatedMesh(scene::IMesh *mesh = 0, scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) : diff --git a/irr/include/SMesh.h b/irr/include/SMesh.h index 66e6ecc08..15fa65115 100644 --- a/irr/include/SMesh.h +++ b/irr/include/SMesh.h @@ -14,7 +14,7 @@ namespace irr namespace scene { //! Simple implementation of the IMesh interface. -struct SMesh : public IMesh +struct SMesh final : public IMesh { //! constructor SMesh() diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index fa6eae63a..303207d93 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -16,7 +16,7 @@ namespace scene { //! A mesh buffer able to choose between S3DVertex2TCoords, S3DVertex and S3DVertexTangents at runtime -struct SSkinMeshBuffer : public IMeshBuffer +struct SSkinMeshBuffer final : public IMeshBuffer { //! Default constructor SSkinMeshBuffer(video::E_VERTEX_TYPE vt = video::EVT_STANDARD) : diff --git a/irr/src/CFileSystem.h b/irr/src/CFileSystem.h index 208a1ac41..9400d85a3 100644 --- a/irr/src/CFileSystem.h +++ b/irr/src/CFileSystem.h @@ -17,7 +17,7 @@ class CZipReader; /*! FileSystem which uses normal files and one zipfile */ -class CFileSystem : public IFileSystem +class CFileSystem final : public IFileSystem { public: //! constructor diff --git a/irr/src/CImage.h b/irr/src/CImage.h index 955f85705..33a34386e 100644 --- a/irr/src/CImage.h +++ b/irr/src/CImage.h @@ -21,7 +21,7 @@ inline bool checkImageDimensions(u32 width, u32 height) //! IImage implementation with a lot of special image operations for //! 16 bit A1R5G5B5/32 Bit A8R8G8B8 images, which are used by the SoftwareDevice. -class CImage : public IImage +class CImage final : public IImage { public: //! constructor from raw image data diff --git a/irr/src/CLimitReadFile.h b/irr/src/CLimitReadFile.h index 1594135e6..6b02cfdc0 100644 --- a/irr/src/CLimitReadFile.h +++ b/irr/src/CLimitReadFile.h @@ -20,7 +20,7 @@ namespace io This can be useful, for example for reading uncompressed files in an archive (zip, tar). !*/ -class CLimitReadFile : public IReadFile +class CLimitReadFile final : public IReadFile { public: CLimitReadFile(IReadFile *alreadyOpenedFile, long pos, long areaSize, const io::path &name); diff --git a/irr/src/CMemoryFile.h b/irr/src/CMemoryFile.h index 3ce0e8460..83d77cb77 100644 --- a/irr/src/CMemoryFile.h +++ b/irr/src/CMemoryFile.h @@ -17,7 +17,7 @@ namespace io /*! Class for reading from memory. */ -class CMemoryReadFile : public IMemoryReadFile +class CMemoryReadFile final : public IMemoryReadFile { public: //! Constructor diff --git a/irr/src/CReadFile.h b/irr/src/CReadFile.h index 14f674772..c9231be7c 100644 --- a/irr/src/CReadFile.h +++ b/irr/src/CReadFile.h @@ -17,7 +17,7 @@ namespace io /*! Class for reading a real file from disk. */ -class CReadFile : public IReadFile +class CReadFile final : public IReadFile { public: CReadFile(const io::path &fileName); diff --git a/irr/src/CSceneManager.h b/irr/src/CSceneManager.h index 32df145ec..4ef6d64b0 100644 --- a/irr/src/CSceneManager.h +++ b/irr/src/CSceneManager.h @@ -25,7 +25,7 @@ class IMeshCache; /*! The Scene Manager manages scene nodes, mesh resources, cameras and all the other stuff. */ -class CSceneManager : public ISceneManager, public ISceneNode +class CSceneManager final : public ISceneManager, public ISceneNode { public: //! constructor diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index ba85b30ad..daf01c488 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -269,11 +269,12 @@ public: JoystickController joystick; KeyCache keycache; }; + /* - Separated input handler + Separated input handler implementations */ -class RealInputHandler : public InputHandler +class RealInputHandler final : public InputHandler { public: RealInputHandler(MyEventReceiver *receiver) : m_receiver(receiver) @@ -372,7 +373,7 @@ private: v2s32 m_mousepos; }; -class RandomInputHandler : public InputHandler +class RandomInputHandler final : public InputHandler { public: RandomInputHandler() = default; diff --git a/src/client/texturesource.cpp b/src/client/texturesource.cpp index 12a21771a..a4222f414 100644 --- a/src/client/texturesource.cpp +++ b/src/client/texturesource.cpp @@ -39,7 +39,7 @@ struct TextureInfo }; // TextureSource -class TextureSource : public IWritableTextureSource +class TextureSource final : public IWritableTextureSource { public: TextureSource(); diff --git a/src/network/mtp/impl.h b/src/network/mtp/impl.h index cc1d4c2ed..7105bac6d 100644 --- a/src/network/mtp/impl.h +++ b/src/network/mtp/impl.h @@ -234,7 +234,7 @@ class Peer : public IPeer { class UDPPeer; -class Connection : public IConnection +class Connection final : public IConnection { public: friend class ConnectionSendThread; From 2bc9dc54ff9974c5fb04613022f8527e010bd1a9 Mon Sep 17 00:00:00 2001 From: Zughy <63455151+Zughy@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:50:28 +0200 Subject: [PATCH 074/200] Windows/vcpkg instructions: enable i18n by default --- doc/compiling/windows.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/compiling/windows.md b/doc/compiling/windows.md index c63a7b319..eeaf2e4fd 100644 --- a/doc/compiling/windows.md +++ b/doc/compiling/windows.md @@ -14,7 +14,7 @@ It is highly recommended to use vcpkg as package manager. After you successfully built vcpkg you can easily install the required libraries: ```powershell -vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp gettext sdl2 --triplet x64-windows +vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp gettext[tools] sdl2 --triplet x64-windows ``` - `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store. @@ -52,7 +52,7 @@ Use `--triplet` to specify the target triplet, e.g. `x64-windows` or `x86-window Run the following script in PowerShell: ```powershell -cmake . -G"Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=OFF -DENABLE_CURSES=OFF +cmake . -G"Visual Studio 16 2019" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_CURSES=OFF cmake --build . --config Release ``` Make sure that the right compiler is selected and the path to the vcpkg toolchain is correct. From d5d8fb629b69df57573dc2ef4043b287f6a0c5f0 Mon Sep 17 00:00:00 2001 From: red-001 Date: Mon, 2 Sep 2024 20:50:43 +0100 Subject: [PATCH 075/200] Simplify `TOSERVER_INIT` and `TOCLIENT_HELLO` - Network compression support was never added. - Client hasn't used the returned playername since at least 0.4-stable. --- src/client/client.cpp | 5 +---- src/network/clientpackethandler.cpp | 13 ++++--------- src/network/networkprotocol.h | 13 +++---------- src/network/serverpackethandler.cpp | 16 +++++----------- src/server/clientiface.h | 5 ----- 5 files changed, 13 insertions(+), 39 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index d3ad62512..dedbebbb4 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1141,10 +1141,7 @@ void Client::sendInit(const std::string &playerName) { NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size())); - // we don't support network compression yet - u16 supp_comp_modes = NETPROTO_COMPRESSION_NONE; - - pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) supp_comp_modes; + pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) 0; pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX; pkt << playerName; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 4b9e8ceea..4eefd1c59 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -78,11 +78,11 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) u8 serialization_ver; u16 proto_ver; - u16 compression_mode; + u16 unused_compression_mode; u32 auth_mechs; - std::string username_legacy; // for case insensitivity - *pkt >> serialization_ver >> compression_mode >> proto_ver - >> auth_mechs >> username_legacy; + std::string unused; + *pkt >> serialization_ver >> unused_compression_mode >> proto_ver + >> auth_mechs >> unused; // Chose an auth method we support AuthMechanism chosen_auth_mechanism = choseAuthMech(auth_mechs); @@ -91,7 +91,6 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) << "serialization_ver=" << (u32)serialization_ver << ", auth_mechs=" << auth_mechs << ", proto_ver=" << proto_ver - << ", compression_mode=" << compression_mode << ". Doing auth with mech " << chosen_auth_mechanism << std::endl; if (!ser_ver_supported(serialization_ver)) { @@ -103,10 +102,6 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) m_server_ser_ver = serialization_ver; m_proto_ver = proto_ver; - //TODO verify that username_legacy matches sent username, only - // differs in casing (make both uppercase and compare) - // This is only necessary though when we actually want to add casing support - if (m_chosen_auth_mech != AUTH_MECHANISM_NONE) { // we received a TOCLIENT_HELLO while auth was already going on errorstream << "Client: TOCLIENT_HELLO while auth was already going on" diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 467124f60..5af9859ae 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -243,9 +243,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CLIENT_PROTOCOL_VERSION_MIN 37 #define CLIENT_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION -#define PASSWORD_SIZE 28 // Maximum password length. Allows for - // base64-encoded SHA-1 (27+\0). - // See also formspec [Version History] in doc/lua_api.md #define FORMSPEC_API_VERSION 7 @@ -260,10 +257,10 @@ enum ToClientCommand : u16 Sent after TOSERVER_INIT. u8 deployed serialization version - u16 deployed network compression mode + u16 unused (network compression, never implemeneted) u16 deployed protocol version u32 supported auth methods - std::string username that should be used for legacy hash (for proper casing) + std::string unused (used to be username) */ TOCLIENT_AUTH_ACCEPT = 0x03, /* @@ -914,7 +911,7 @@ enum ToServerCommand : u16 Sent first after connected. u8 serialization version (=SER_FMT_VER_HIGHEST_READ) - u16 supported network compression modes + u16 unused (supported network compression modes, never implemeneted) u16 minimum supported network protocol version u16 maximum supported network protocol version std::string player name @@ -1149,10 +1146,6 @@ enum AccessDeniedCode : u8 { SERVER_ACCESSDENIED_MAX, }; -enum NetProtoCompressionMode { - NETPROTO_COMPRESSION_NONE = 0, -}; - enum PlayerListModifer : u8 { PLAYER_LIST_INIT, diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index c0718d43b..7d8555c8e 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -101,12 +101,12 @@ void Server::handleCommand_Init(NetworkPacket* pkt) // First byte after command is maximum supported // serialization version u8 client_max; - u16 supp_compr_modes; + u16 unused; u16 min_net_proto_version = 0; u16 max_net_proto_version; std::string playerName; - *pkt >> client_max >> supp_compr_modes >> min_net_proto_version + *pkt >> client_max >> unused >> min_net_proto_version >> max_net_proto_version >> playerName; u8 our_max = SER_FMT_VER_HIGHEST_READ; @@ -190,9 +190,6 @@ void Server::handleCommand_Init(NetworkPacket* pkt) } m_clients.setPlayerName(peer_id, playername); - //TODO (later) case insensitivity - - std::string legacyPlayerNameCasing = playerName; if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) { actionstream << "Server: Player with the name \"singleplayer\" tried " @@ -279,17 +276,14 @@ void Server::handleCommand_Init(NetworkPacket* pkt) verbosestream << "Sending TOCLIENT_HELLO with auth method field: " << auth_mechs << std::endl; - NetworkPacket resp_pkt(TOCLIENT_HELLO, - 1 + 4 + legacyPlayerNameCasing.size(), peer_id); + NetworkPacket resp_pkt(TOCLIENT_HELLO, 0, peer_id); - u16 depl_compress_mode = NETPROTO_COMPRESSION_NONE; - resp_pkt << depl_serial_v << depl_compress_mode << net_proto_version - << auth_mechs << legacyPlayerNameCasing; + resp_pkt << depl_serial_v << u16(0) << net_proto_version + << auth_mechs << std::string_view(); Send(&resp_pkt); client->allowed_auth_mechs = auth_mechs; - client->setDeployedCompressionMode(depl_compress_mode); m_clients.event(peer_id, CSE_Hello); } diff --git a/src/server/clientiface.h b/src/server/clientiface.h index d4d21ceb2..f930e7f3e 100644 --- a/src/server/clientiface.h +++ b/src/server/clientiface.h @@ -321,9 +321,6 @@ public: void setPendingSerializationVersion(u8 version) { m_pending_serialization_version = version; } - void setDeployedCompressionMode(u16 byteFlag) - { m_deployed_compression = byteFlag; } - void confirmSerializationVersion() { serialization_version = m_pending_serialization_version; } @@ -449,8 +446,6 @@ private: std::string m_full_version = "unknown"; - u16 m_deployed_compression = 0; - /* time this client was created */ From 2e567b7d40ef99e48fb6acc5adef42c986720943 Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Mon, 19 Aug 2024 16:00:24 +0200 Subject: [PATCH 076/200] Replace removed rare_controls.png in Devtest /test_formspec removed by 013c6ee1663f2bdd93a6d30690a96a5914dc27dc / #14918 --- games/devtest/mods/testformspec/formspec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/games/devtest/mods/testformspec/formspec.lua b/games/devtest/mods/testformspec/formspec.lua index 99ee691f1..8d0b759f5 100644 --- a/games/devtest/mods/testformspec/formspec.lua +++ b/games/devtest/mods/testformspec/formspec.lua @@ -66,7 +66,7 @@ local inv_style_fs = [[ -- Some textures from textures/base/pack and Devtest, with many different sizes -- and aspect ratios. -local image_column = "image,0=logo.png,1=rare_controls.png,2=checkbox_16.png," .. +local image_column = "image,0=logo.png,1=crack_anylength.png^[invert:rgb,2=checkbox_16.png," .. "3=checkbox_32.png,4=checkbox_64.png,5=default_lava.png," .. "6=progress_bar.png,7=progress_bar_bg.png" local words = { From 88397c2908ce2ab53adf107f0653600def2296b5 Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Mon, 19 Aug 2024 16:06:26 +0200 Subject: [PATCH 077/200] TouchScreenGUI: Don't release pointers when toggling grid menu --- src/gui/touchcontrols.cpp | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index 1e4f8a99b..7d7cd6e09 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -477,16 +477,13 @@ void TouchControls::handleReleaseEvent(size_t pointer_id) m_pointer_downpos.erase(pointer_id); m_pointer_pos.erase(pointer_id); - if (m_overflow_open) { - buttons_handleRelease(m_overflow_buttons, pointer_id, m_device->getVideoDriver(), - m_receiver, m_texturesource); - return; - } - // handle buttons if (buttons_handleRelease(m_buttons, pointer_id, m_device->getVideoDriver(), m_receiver, m_texturesource)) return; + if (buttons_handleRelease(m_overflow_buttons, pointer_id, m_device->getVideoDriver(), + m_receiver, m_texturesource)) + return; if (m_has_move_id && pointer_id == m_move_id) { // handle the point used for moving view @@ -513,9 +510,7 @@ void TouchControls::handleReleaseEvent(size_t pointer_id) m_joystick_status_aux1 = false; applyJoystickStatus(); - m_joystick_btn_off->setVisible(true); - m_joystick_btn_bg->setVisible(false); - m_joystick_btn_center->setVisible(false); + updateVisibility(); } else { infostream << "TouchControls::translateEvent released unknown button: " << pointer_id << std::endl; @@ -572,9 +567,6 @@ void TouchControls::translateEvent(const SEvent &event) toggleOverflowMenu(); // refresh since visibility of buttons has changed element = m_guienv->getRootGUIElement()->getElementFromPoint(touch_pos); - // restore after releaseAll in toggleOverflowMenu - m_pointer_downpos[pointer_id] = touch_pos; - m_pointer_pos[pointer_id] = touch_pos; // continue processing, but avoid accidentally placing a node // when closing the overflow menu prevent_short_tap = true; @@ -600,9 +592,7 @@ void TouchControls::translateEvent(const SEvent &event) m_joystick_id = pointer_id; m_joystick_has_really_moved = false; - m_joystick_btn_off->setVisible(false); - m_joystick_btn_bg->setVisible(true); - m_joystick_btn_center->setVisible(true); + updateVisibility(); // If it's a fixed joystick, don't move the joystick "button". if (!m_fixed_joystick) @@ -633,9 +623,6 @@ void TouchControls::translateEvent(const SEvent &event) } else { assert(event.TouchInput.Event == ETIE_MOVED); - if (m_overflow_open) - return; - if (!(m_has_joystick_id && m_fixed_joystick) && m_pointer_pos[event.TouchInput.ID] == touch_pos) return; @@ -721,13 +708,9 @@ void TouchControls::applyJoystickStatus() void TouchControls::step(float dtime) { - if (m_overflow_open) { - buttons_step(m_overflow_buttons, dtime, m_device->getVideoDriver(), m_receiver, m_texturesource); - return; - } - // simulate keyboard repeats buttons_step(m_buttons, dtime, m_device->getVideoDriver(), m_receiver, m_texturesource); + buttons_step(m_overflow_buttons, dtime, m_device->getVideoDriver(), m_receiver, m_texturesource); // joystick applyJoystickStatus(); @@ -774,7 +757,6 @@ void TouchControls::setVisible(bool visible) return; m_visible = visible; - // order matters if (!visible) { releaseAll(); m_overflow_open = false; @@ -784,7 +766,8 @@ void TouchControls::setVisible(bool visible) void TouchControls::toggleOverflowMenu() { - releaseAll(); // must be done first + // no releaseAll here so that you can e.g. continue holding the joystick + // while the overflow menu is open m_overflow_open = !m_overflow_open; updateVisibility(); } @@ -795,7 +778,10 @@ void TouchControls::updateVisibility() for (auto &button : m_buttons) button.gui_button->setVisible(regular_visible); m_overflow_btn->setVisible(regular_visible); - m_joystick_btn_off->setVisible(regular_visible); + + m_joystick_btn_off->setVisible(regular_visible && !m_has_joystick_id); + m_joystick_btn_bg->setVisible(regular_visible && m_has_joystick_id); + m_joystick_btn_center->setVisible(regular_visible && m_has_joystick_id); bool overflow_visible = m_visible && m_overflow_open; m_overflow_bg->setVisible(overflow_visible); From 08de047033da30255faf6685059c96a1c31d8bb1 Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Wed, 21 Aug 2024 13:04:51 +0200 Subject: [PATCH 078/200] TouchScreenGUI: Show status text above grid menu --- src/client/gameui.cpp | 28 ++++++++++++++++++++-------- src/gui/touchcontrols.cpp | 4 ++++ src/gui/touchcontrols.h | 5 +++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 7631f9c78..41071ef66 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/mainmenumanager.h" #include "gui/guiChatConsole.h" #include "gui/guiFormSpecMenu.h" +#include "gui/touchcontrols.h" #include "util/enriched_string.h" #include "util/pointedthing.h" #include "client.h" @@ -191,16 +192,27 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ } } - setStaticText(m_guitext_status, m_statustext.c_str()); - m_guitext_status->setVisible(!m_statustext.empty()); + IGUIStaticText *guitext_status; + bool overriden = g_touchcontrols && g_touchcontrols->isStatusTextOverriden(); + if (overriden) { + guitext_status = g_touchcontrols->getStatusText(); + m_guitext_status->setVisible(false); + } else { + guitext_status = m_guitext_status; + if (g_touchcontrols) + g_touchcontrols->getStatusText()->setVisible(false); + } + + setStaticText(guitext_status, m_statustext.c_str()); + guitext_status->setVisible(!m_statustext.empty()); if (!m_statustext.empty()) { - s32 status_width = m_guitext_status->getTextWidth(); - s32 status_height = m_guitext_status->getTextHeight(); - s32 status_y = screensize.Y - 150; + s32 status_width = guitext_status->getTextWidth(); + s32 status_height = guitext_status->getTextHeight(); + s32 status_y = screensize.Y - (overriden ? 15 : 150); s32 status_x = (screensize.X - status_width) / 2; - m_guitext_status->setRelativePosition(core::rect(status_x , + guitext_status->setRelativePosition(core::rect(status_x , status_y - status_height, status_x + status_width, status_y)); // Fade out @@ -208,8 +220,8 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ final_color.setAlpha(0); video::SColor fade_color = m_statustext_initial_color.getInterpolated_quadratic( m_statustext_initial_color, final_color, m_statustext_time / statustext_time_max); - m_guitext_status->setOverrideColor(fade_color); - m_guitext_status->enableOverrideColor(true); + guitext_status->setOverrideColor(fade_color); + guitext_status->enableOverrideColor(true); } // Hide chat when disabled by server or when console is visible diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index 7d7cd6e09..4a673ccf3 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -412,6 +412,10 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc) pos.X += spacing.X; } + + m_status_text = grab_gui_element( + m_guienv->addStaticText(L"", recti(), false, false)); + m_status_text->setVisible(false); } void TouchControls::addButton(std::vector &buttons, touch_gui_button_id id, diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index 0a86fe34e..102c85f09 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -177,6 +177,9 @@ public: void registerHotbarRect(u16 index, const recti &rect); std::optional getHotbarSelection(); + bool isStatusTextOverriden() { return m_overflow_open; } + IGUIStaticText *getStatusText() { return m_status_text.get(); } + private: IrrlichtDevice *m_device = nullptr; IGUIEnvironment *m_guienv = nullptr; @@ -235,6 +238,8 @@ private: std::vector> m_overflow_button_titles; std::vector m_overflow_button_rects; + std::shared_ptr m_status_text; + void toggleOverflowMenu(); void updateVisibility(); void releaseAll(); From 83498463336b56a7ad5440060f7daa7723e04246 Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 4 Sep 2024 15:18:45 +0200 Subject: [PATCH 079/200] TouchControls: Fix setUseCrosshair not being called (#15100) --- src/client/game.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 0a9b4ec58..7213faafa 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1510,10 +1510,6 @@ bool Game::createClient(const GameStartData &start_data) client->getScript()->on_camera_ready(camera); client->setCamera(camera); - if (g_touchcontrols) { - g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); - } - /* Clouds */ if (m_cache_enable_clouds) @@ -1578,8 +1574,10 @@ bool Game::initGui() gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, chat_backend, client, &g_menumgr); - if (g_settings->getBool("touch_controls")) + if (g_settings->getBool("touch_controls")) { g_touchcontrols = new TouchControls(device, texture_src); + g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); + } return true; } From 074700b35eab0d0a3fe57e959889b92aa97ae157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:19:00 +0200 Subject: [PATCH 080/200] Remove no* prefixes from settingtypes possible flags (#15111) --- builtin/settingtypes.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index d03634506..54a639a89 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1027,7 +1027,7 @@ mapgen_limit (Map generation limit) int 31007 0 31007 # Global map generation attributes. # In Mapgen v6 the 'decorations' flag controls all decorations except trees # and jungle grass, in all other mapgens this flag controls all decorations. -mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores,nocaves,nodungeons,nolight,nodecorations,nobiomes,noores +mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores [*Biome API] @@ -1046,7 +1046,7 @@ mg_biome_np_humidity_blend (Humidity blend noise) noise_params_2d 0, 1.5, (8, 8, [*Mapgen V5] # Map generation attributes specific to Mapgen v5. -mgv5_spflags (Mapgen V5 specific flags) flags caverns caverns,nocaverns +mgv5_spflags (Mapgen V5 specific flags) flags caverns caverns # Controls width of tunnels, a smaller value creates wider tunnels. # Value >= 10.0 completely disables generation of tunnels and avoids the @@ -1120,7 +1120,7 @@ mgv5_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2 # When the 'snowbiomes' flag is enabled jungles are automatically enabled and # the 'jungles' flag is ignored. # The 'temples' flag disables generation of desert temples. Normal dungeons will appear instead. -mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees,temples jungles,biomeblend,mudflow,snowbiomes,flat,trees,temples,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees,notemples +mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees,temples jungles,biomeblend,mudflow,snowbiomes,flat,trees,temples # Deserts occur when np_biome exceeds this value. # When the 'snowbiomes' flag is enabled, this is ignored. @@ -1176,7 +1176,7 @@ mgv6_np_apple_trees (Apple trees noise) noise_params_2d 0, 1, (100, 100, 100), 3 # 'ridges': Rivers. # 'floatlands': Floating land masses in the atmosphere. # 'caverns': Giant caves deep underground. -mgv7_spflags (Mapgen V7 specific flags) flags mountains,ridges,nofloatlands,caverns mountains,ridges,floatlands,caverns,nomountains,noridges,nofloatlands,nocaverns +mgv7_spflags (Mapgen V7 specific flags) flags mountains,ridges,nofloatlands,caverns mountains,ridges,floatlands,caverns # Y of mountain density gradient zero level. Used to shift mountains vertically. mgv7_mount_zero_level (Mountain zero level) int 0 -31000 31000 @@ -1310,7 +1310,7 @@ mgv7_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2 [*Mapgen Carpathian] # Map generation attributes specific to Mapgen Carpathian. -mgcarpathian_spflags (Mapgen Carpathian specific flags) flags caverns,norivers caverns,rivers,nocaverns,norivers +mgcarpathian_spflags (Mapgen Carpathian specific flags) flags caverns,norivers caverns,rivers # Defines the base ground level. mgcarpathian_base_level (Base ground level) float 12.0 @@ -1419,7 +1419,7 @@ mgcarpathian_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 50 # Map generation attributes specific to Mapgen Flat. # Occasional lakes and hills can be added to the flat world. -mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills,nocaverns lakes,hills,caverns,nolakes,nohills,nocaverns +mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills,nocaverns lakes,hills,caverns # Y of flat ground. mgflat_ground_level (Ground level) int 8 -31000 31000 @@ -1503,7 +1503,7 @@ mgflat_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, # Map generation attributes specific to Mapgen Fractal. # 'terrain' enables the generation of non-fractal terrain: # ocean, islands and underground. -mgfractal_spflags (Mapgen Fractal specific flags) flags terrain terrain,noterrain +mgfractal_spflags (Mapgen Fractal specific flags) flags terrain terrain # Controls width of tunnels, a smaller value creates wider tunnels. # Value >= 10.0 completely disables generation of tunnels and avoids the @@ -1637,7 +1637,7 @@ mgfractal_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), # 'vary_river_depth': If enabled, low humidity and high heat causes rivers # to become shallower and occasionally dry. # 'altitude_dry': Reduces humidity with altitude. -mgvalleys_spflags (Mapgen Valleys specific flags) flags altitude_chill,humid_rivers,vary_river_depth,altitude_dry altitude_chill,humid_rivers,vary_river_depth,altitude_dry,noaltitude_chill,nohumid_rivers,novary_river_depth,noaltitude_dry +mgvalleys_spflags (Mapgen Valleys specific flags) flags altitude_chill,humid_rivers,vary_river_depth,altitude_dry altitude_chill,humid_rivers,vary_river_depth,altitude_dry # The vertical distance over which heat drops by 20 if 'altitude_chill' is # enabled. Also, the vertical distance over which humidity drops by 10 if From 486dc3288d7b8e8c55bbc20dea11fef32fd7119b Mon Sep 17 00:00:00 2001 From: red-001 Date: Wed, 4 Sep 2024 14:20:39 +0100 Subject: [PATCH 081/200] VoxelManipulator code cleanup (#15114) * Cache node in voxel area index when possible The index function according to the MSVC profiler actually takes up a significant time slice (around ~5% of total time for the process) during normal game-play. Might not be accurate but still good to not recalculate it twice. * Remove `setNodeNoRef` from VM * VM: remove old commented out print statement --- src/unittest/test_voxelmanipulator.cpp | 2 +- src/voxel.h | 33 ++++++++++++-------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/unittest/test_voxelmanipulator.cpp b/src/unittest/test_voxelmanipulator.cpp index acc2707e7..6ea0ca9af 100644 --- a/src/unittest/test_voxelmanipulator.cpp +++ b/src/unittest/test_voxelmanipulator.cpp @@ -87,7 +87,7 @@ void TestVoxelManipulator::testVoxelManipulator(const NodeDefManager *nodedef) v.print(infostream, nodedef); infostream << "*** Setting (-1,0,-1)=2 ***" << std::endl; - v.setNodeNoRef(v3s16(-1,0,-1), MapNode(t_CONTENT_GRASS)); + v.setNode(v3s16(-1,0,-1), MapNode(t_CONTENT_GRASS)); v.print(infostream, nodedef); UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == t_CONTENT_GRASS); diff --git a/src/voxel.h b/src/voxel.h index 286e09abe..882a59b77 100644 --- a/src/voxel.h +++ b/src/voxel.h @@ -392,36 +392,36 @@ public: VoxelArea voxel_area(p); addArea(voxel_area); - if (m_flags[m_area.index(p)] & VOXELFLAG_NO_DATA) { - /*dstream<<"EXCEPT: VoxelManipulator::getNode(): " - <<"p=("< Date: Fri, 6 Sep 2024 11:30:10 +0200 Subject: [PATCH 082/200] Refactor "Cavegen y biome check" --- src/mapgen/cavegen.cpp | 16 +++++----------- src/mapgen/mapgen.cpp | 15 ++------------- src/mapgen/mg_biome.cpp | 35 ++++++++++++----------------------- src/mapgen/mg_biome.h | 14 ++++++++++---- 4 files changed, 29 insertions(+), 51 deletions(-) diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp index 47272142f..e6ab66980 100644 --- a/src/mapgen/cavegen.cpp +++ b/src/mapgen/cavegen.cpp @@ -82,8 +82,6 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm, const v3s16 &em = vm->m_area.getExtent(); u32 index2d = 0; // Biomemap index - s16 *biome_transitions = m_bmgn->getBiomeTransitions(); - for (s16 z = nmin.Z; z <= nmax.Z; z++) for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) { bool column_is_open = false; // Is column open to overground @@ -101,8 +99,7 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm, u16 depth_riverbed = biome->depth_riverbed; u16 nplaced = 0; - int cur_biome_depth = 0; - s16 biome_y_min = biome_transitions[cur_biome_depth]; + s16 biome_y_min = m_bmgn->getNextTransitionY(nmax.Y); // Don't excavate the overgenerated stone at nmax.Y + 1, // this creates a 'roof' over the tunnel, preventing light in @@ -114,15 +111,12 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm, // We need this check to make sure that biomes don't generate too far down if (y < biome_y_min) { biome = m_bmgn->getBiomeAtIndex(index2d, v3s16(x, y, z)); + biome_y_min = m_bmgn->getNextTransitionY(y); - // Finding the height of the next biome - // On first iteration this may loop a couple times after than it should just run once - while (y < biome_y_min) { - biome_y_min = biome_transitions[++cur_biome_depth]; + if (x == nmin.X && z == nmin.Z && false) { + dstream << "cavegen: biome at " << y << " is " << biome->name + << ", next at " << biome_y_min << std::endl; } - - /*if (x == nmin.X && z == nmin.Z) - printf("Cave: check @ %i -> %s -> again at %i\n", y, biome->name.c_str(), biome_y_min);*/ } content_t c = vm->m_data[vi].getContent(); diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index 80ffebc9e..0b821e02e 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -649,8 +649,6 @@ void MapgenBasic::generateBiomes() noise_filler_depth->perlinMap2D(node_min.X, node_min.Z); - s16 *biome_transitions = biomegen->getBiomeTransitions(); - for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index++) { Biome *biome = NULL; @@ -661,8 +659,7 @@ void MapgenBasic::generateBiomes() u16 depth_riverbed = 0; u32 vi = vm->m_area.index(x, node_max.Y, z); - int cur_biome_depth = 0; - s16 biome_y_min = biome_transitions[cur_biome_depth]; + s16 biome_y_min = biomegen->getNextTransitionY(node_max.Y); // Check node at base of mapchunk above, either a node of a previously // generated mapchunk or if not, a node of overgenerated base terrain. @@ -695,15 +692,7 @@ void MapgenBasic::generateBiomes() if (!biome || y < biome_y_min) { // (Re)calculate biome biome = biomegen->getBiomeAtIndex(index, v3s16(x, y, z)); - - // Finding the height of the next biome - // On first iteration this may loop a couple times after than it should just run once - while (y < biome_y_min) { - biome_y_min = biome_transitions[++cur_biome_depth]; - } - - /*if (x == node_min.X && z == node_min.Z) - printf("Map: check @ %i -> %s -> again at %i\n", y, biome->name.c_str(), biome_y_min);*/ + biome_y_min = biomegen->getNextTransitionY(y); } // Add biome to biomemap at first stone surface detected diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp index 83ecbd691..b270a5413 100644 --- a/src/mapgen/mg_biome.cpp +++ b/src/mapgen/mg_biome.cpp @@ -149,47 +149,36 @@ BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr, // is disabled. memset(biomemap, 0, sizeof(biome_t) * m_csize.X * m_csize.Z); - // Calculating the bounding position of each biome so we know when we might switch - // First gathering all heights where we might switch - std::vector temp_transition_heights; - temp_transition_heights.reserve(m_bmgr->getNumObjects() * 2); + // Calculate cache of Y transition points + std::vector values; + values.reserve(m_bmgr->getNumObjects() * 2); for (size_t i = 0; i < m_bmgr->getNumObjects(); i++) { Biome *b = (Biome *)m_bmgr->getRaw(i); - temp_transition_heights.push_back(b->max_pos.Y); - temp_transition_heights.push_back(b->min_pos.Y); + values.push_back(b->max_pos.Y); + values.push_back(b->min_pos.Y); } - // Sorting the biome transition points - std::sort(temp_transition_heights.begin(), temp_transition_heights.end(), std::greater()); + std::sort(values.begin(), values.end(), std::greater<>()); + values.erase(std::unique(values.begin(), values.end()), values.end()); - // Getting rid of duplicate biome transition points - s16 last = temp_transition_heights[0]; - size_t out_pos = 1; - for (size_t i = 1; i < temp_transition_heights.size(); i++){ - if (temp_transition_heights[i] != last) { - last = temp_transition_heights[i]; - temp_transition_heights[out_pos++] = last; - } - } - - biome_transitions = new s16[out_pos]; - memcpy(biome_transitions, temp_transition_heights.data(), sizeof(s16) * out_pos); + m_transitions_y = std::move(values); } BiomeGenOriginal::~BiomeGenOriginal() { delete []biomemap; - delete []biome_transitions; delete noise_heat; delete noise_humidity; delete noise_heat_blend; delete noise_humidity_blend; } -s16* BiomeGenOriginal::getBiomeTransitions() const +s16 BiomeGenOriginal::getNextTransitionY(s16 y) const { - return biome_transitions; + // Find first value that is less than y using binary search + auto it = std::lower_bound(m_transitions_y.begin(), m_transitions_y.end(), y, std::greater_equal<>()); + return (it == m_transitions_y.end()) ? S16_MIN : *it; } BiomeGen *BiomeGenOriginal::clone(BiomeManager *biomemgr) const diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h index 567a0fe81..389b36ee9 100644 --- a/src/mapgen/mg_biome.h +++ b/src/mapgen/mg_biome.h @@ -128,11 +128,14 @@ public: // Same as above, but uses a raw numeric index correlating to the (x,z) position. virtual Biome *getBiomeAtIndex(size_t index, v3s16 pos) const = 0; - virtual s16 *getBiomeTransitions() const = 0; + // Returns the next lower y position at which the biome could change. + // You can use this to optimize calls to getBiomeAtIndex(). + virtual s16 getNextTransitionY(s16 y) const { + return y == S16_MIN ? y : (y - 1); + }; // Result of calcBiomes bulk computation. biome_t *biomemap = nullptr; - s16 *biome_transitions = nullptr; protected: BiomeManager *m_bmgr = nullptr; @@ -167,7 +170,7 @@ struct BiomeParamsOriginal : public BiomeParams { NoiseParams np_humidity_blend; }; -class BiomeGenOriginal : public BiomeGen { +class BiomeGenOriginal final : public BiomeGen { public: BiomeGenOriginal(BiomeManager *biomemgr, const BiomeParamsOriginal *params, v3s16 chunksize); @@ -189,7 +192,7 @@ public: Biome *getBiomeAtIndex(size_t index, v3s16 pos) const; Biome *calcBiomeFromNoise(float heat, float humidity, v3s16 pos) const; - s16 *getBiomeTransitions() const; + s16 getNextTransitionY(s16 y) const; float *heatmap; float *humidmap; @@ -201,6 +204,9 @@ private: Noise *noise_humidity; Noise *noise_heat_blend; Noise *noise_humidity_blend; + + // ordered descending + std::vector m_transitions_y; }; From 4fd744cdf6ef100cbcdaf6f55eca2af624fd9d84 Mon Sep 17 00:00:00 2001 From: sfence Date: Fri, 6 Sep 2024 11:30:27 +0200 Subject: [PATCH 083/200] Generate Minetest.app on macOS 12, so at least macOS 12 will be supported --- .github/workflows/macos.yml | 4 ++-- src/util/numeric.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 34556ce8c..e193c828d 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -29,8 +29,8 @@ on: jobs: build: - # use macOS 13 since it's the last one that still runs on x86 - runs-on: macos-13 + # use lowest possible macOS running on x86_64 to support more users + runs-on: macos-12 steps: - uses: actions/checkout@v4 - name: Install deps diff --git a/src/util/numeric.h b/src/util/numeric.h index cfe0317a1..758b55968 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "SColor.h" #include #include +#include #define rangelim(d, min, max) ((d) < (min) ? (min) : ((d) > (max) ? (max) : (d))) #define myfloor(x) ((x) < 0.0 ? (int)(x) - 1 : (int)(x)) From 197d09cc534b61b01384ef5c97c05035a323e4d7 Mon Sep 17 00:00:00 2001 From: red-001 Date: Mon, 2 Sep 2024 05:56:53 +0100 Subject: [PATCH 084/200] SRP switch to porting randomness source --- src/util/srp.cpp | 70 +++++------------------------------------------- 1 file changed, 7 insertions(+), 63 deletions(-) diff --git a/src/util/srp.cpp b/src/util/srp.cpp index 56b2aa763..3c7b6de36 100644 --- a/src/util/srp.cpp +++ b/src/util/srp.cpp @@ -51,6 +51,7 @@ #endif #include "my_sha256.h" +#include "porting.h" #include "srp.h" //#define CSRP_USE_SHA1 @@ -70,12 +71,6 @@ printf("\n"); }*/ -static int g_initialized = 0; - -#define RAND_BUFF_MAX 128 -static unsigned int g_rand_idx; -static unsigned char g_rand_buff[RAND_BUFF_MAX]; - void *(*srp_alloc)(size_t) = &malloc; void *(*srp_realloc)(void *, size_t) = &realloc; void (*srp_free)(void *) = &free; @@ -521,52 +516,15 @@ static SRP_Result calculate_H_AMK(SRP_HashAlgorithm alg, unsigned char *dest, return SRP_OK; } -static SRP_Result fill_buff() -{ - g_rand_idx = 0; - -#ifdef WIN32 - HCRYPTPROV wctx; -#else - FILE *fp = 0; -#endif - -#ifdef WIN32 - - if (!CryptAcquireContext(&wctx, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) - return SRP_ERR; - if (!CryptGenRandom(wctx, sizeof(g_rand_buff), (BYTE *)g_rand_buff)) return SRP_ERR; - if (!CryptReleaseContext(wctx, 0)) return SRP_ERR; - -#else - fp = fopen("/dev/urandom", "r"); - - if (!fp) return SRP_ERR; - - if (fread(g_rand_buff, sizeof(g_rand_buff), 1, fp) != 1) { fclose(fp); return SRP_ERR; } - if (fclose(fp)) return SRP_ERR; -#endif - return SRP_OK; -} - static SRP_Result mpz_fill_random(mpz_t num) { - // was call: BN_rand(num, 256, -1, 0); - if (RAND_BUFF_MAX - g_rand_idx < 32) - if (fill_buff() != SRP_OK) return SRP_ERR; - mpz_from_bin((const unsigned char *)(&g_rand_buff[g_rand_idx]), 32, num); - g_rand_idx += 32; + unsigned char random_buf[32]; + if (!porting::secure_rand_fill_buf(random_buf, sizeof(random_buf))) + return SRP_ERR; + mpz_from_bin(random_buf, sizeof(random_buf), num); return SRP_OK; } -static SRP_Result init_random() -{ - if (g_initialized) return SRP_OK; - SRP_Result ret = fill_buff(); - g_initialized = (ret == SRP_OK); - return ret; -} - #define srp_dbg_num(num, text) ; /*void srp_dbg_num(mpz_t num, char * prevtext) { @@ -600,18 +558,13 @@ SRP_Result srp_create_salted_verification_key( SRP_HashAlgorithm alg, if (!ng) goto error_and_exit; - if (init_random() != SRP_OK) /* Only happens once */ - goto error_and_exit; - if (*bytes_s == NULL) { size_t size_to_fill = 16; *len_s = size_to_fill; - if (RAND_BUFF_MAX - g_rand_idx < size_to_fill) - if (fill_buff() != SRP_OK) goto error_and_exit; *bytes_s = (unsigned char *)srp_alloc(size_to_fill); if (!*bytes_s) goto error_and_exit; - memcpy(*bytes_s, &g_rand_buff[g_rand_idx], size_to_fill); - g_rand_idx += size_to_fill; + if (!porting::secure_rand_fill_buf(*bytes_s, size_to_fill)) + goto error_and_exit; } if (!calculate_x( @@ -677,12 +630,6 @@ struct SRPVerifier *srp_verifier_new(SRP_HashAlgorithm alg, if (!ver) goto cleanup_and_exit; - if (init_random() != SRP_OK) { /* Only happens once */ - srp_free(ver); - ver = 0; - goto cleanup_and_exit; - } - ver->username = (char *)srp_alloc(ulen); ver->hash_alg = alg; ver->ng = ng; @@ -824,9 +771,6 @@ struct SRPUser *srp_user_new(SRP_HashAlgorithm alg, SRP_NGType ng_type, if (!usr) goto err_exit; - if (init_random() != SRP_OK) /* Only happens once */ - goto err_exit; - usr->hash_alg = alg; usr->ng = new_ng(ng_type, n_hex, g_hex); From 1527cdf6a478d9daa8d9bcab0f5a642d03e0e4cf Mon Sep 17 00:00:00 2001 From: red-001 Date: Mon, 2 Sep 2024 05:58:45 +0100 Subject: [PATCH 085/200] SRP remove custom memory allocator --- src/util/srp.cpp | 92 ++++++++++++++++++++---------------------------- src/util/srp.h | 9 ----- 2 files changed, 39 insertions(+), 62 deletions(-) diff --git a/src/util/srp.cpp b/src/util/srp.cpp index 3c7b6de36..b1dfa76a4 100644 --- a/src/util/srp.cpp +++ b/src/util/srp.cpp @@ -71,20 +71,6 @@ printf("\n"); }*/ -void *(*srp_alloc)(size_t) = &malloc; -void *(*srp_realloc)(void *, size_t) = &realloc; -void (*srp_free)(void *) = &free; - -void srp_set_memory_functions( - void *(*new_srp_alloc)(size_t), - void *(*new_srp_realloc)(void *, size_t), - void (*new_srp_free)(void *)) -{ - srp_alloc = new_srp_alloc; - srp_realloc = new_srp_realloc; - srp_free = new_srp_free; -} - typedef struct { mpz_t N; mpz_t g; @@ -184,13 +170,13 @@ static void delete_ng(NGConstant *ng) if (ng) { mpz_clear(ng->N); mpz_clear(ng->g); - srp_free(ng); + free(ng); } } static NGConstant *new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex) { - NGConstant *ng = (NGConstant *)srp_alloc(sizeof(NGConstant)); + NGConstant *ng = (NGConstant *)malloc(sizeof(NGConstant)); if (!ng) return 0; @@ -397,17 +383,17 @@ static SRP_Result H_nn( size_t len_n1 = mpz_num_bytes(n1); size_t len_n2 = mpz_num_bytes(n2); size_t nbytes = len_N + len_N; - unsigned char *bin = (unsigned char *)srp_alloc(nbytes); + unsigned char *bin = (unsigned char *)malloc(nbytes); if (!bin) return SRP_ERR; if (len_n1 > len_N || len_n2 > len_N) { - srp_free(bin); + free(bin); return SRP_ERR; } memset(bin, 0, nbytes); mpz_to_bin(n1, bin + (len_N - len_n1)); mpz_to_bin(n2, bin + (len_N + len_N - len_n2)); hash(alg, bin, nbytes, buff); - srp_free(bin); + free(bin); mpz_from_bin(buff, hash_length(alg), result); return SRP_OK; } @@ -417,12 +403,12 @@ static SRP_Result H_ns(mpz_t result, SRP_HashAlgorithm alg, const unsigned char { unsigned char buff[CSRP_MAX_HASH]; size_t nbytes = len_n + len_bytes; - unsigned char *bin = (unsigned char *)srp_alloc(nbytes); + unsigned char *bin = (unsigned char *)malloc(nbytes); if (!bin) return SRP_ERR; memcpy(bin, n, len_n); memcpy(bin + len_n, bytes, len_bytes); hash(alg, bin, nbytes, buff); - srp_free(bin); + free(bin); mpz_from_bin(buff, hash_length(alg), result); return SRP_OK; } @@ -449,22 +435,22 @@ static int calculate_x(mpz_t result, SRP_HashAlgorithm alg, const unsigned char static SRP_Result update_hash_n(SRP_HashAlgorithm alg, HashCTX *ctx, const mpz_t n) { size_t len = mpz_num_bytes(n); - unsigned char *n_bytes = (unsigned char *)srp_alloc(len); + unsigned char *n_bytes = (unsigned char *)malloc(len); if (!n_bytes) return SRP_ERR; mpz_to_bin(n, n_bytes); hash_update(alg, ctx, n_bytes, len); - srp_free(n_bytes); + free(n_bytes); return SRP_OK; } static SRP_Result hash_num(SRP_HashAlgorithm alg, const mpz_t n, unsigned char *dest) { int nbytes = mpz_num_bytes(n); - unsigned char *bin = (unsigned char *)srp_alloc(nbytes); + unsigned char *bin = (unsigned char *)malloc(nbytes); if (!bin) return SRP_ERR; mpz_to_bin(n, bin); hash(alg, bin, nbytes, dest); - srp_free(bin); + free(bin); return SRP_OK; } @@ -529,10 +515,10 @@ static SRP_Result mpz_fill_random(mpz_t num) /*void srp_dbg_num(mpz_t num, char * prevtext) { int len_num = mpz_num_bytes(num); - char *bytes_num = (char*) srp_alloc(len_num); + char *bytes_num = (char*) malloc(len_num); mpz_to_bin(num, (unsigned char *) bytes_num); srp_dbg_data(bytes_num, len_num, prevtext); - srp_free(bytes_num); + free(bytes_num); }*/ @@ -561,7 +547,7 @@ SRP_Result srp_create_salted_verification_key( SRP_HashAlgorithm alg, if (*bytes_s == NULL) { size_t size_to_fill = 16; *len_s = size_to_fill; - *bytes_s = (unsigned char *)srp_alloc(size_to_fill); + *bytes_s = (unsigned char *)malloc(size_to_fill); if (!*bytes_s) goto error_and_exit; if (!porting::secure_rand_fill_buf(*bytes_s, size_to_fill)) goto error_and_exit; @@ -577,7 +563,7 @@ SRP_Result srp_create_salted_verification_key( SRP_HashAlgorithm alg, *len_v = mpz_num_bytes(v); - *bytes_v = (unsigned char *)srp_alloc(*len_v); + *bytes_v = (unsigned char *)malloc(*len_v); if (!*bytes_v) goto error_and_exit; @@ -626,16 +612,16 @@ struct SRPVerifier *srp_verifier_new(SRP_HashAlgorithm alg, if (!ng) goto cleanup_and_exit; - ver = (struct SRPVerifier *)srp_alloc(sizeof(struct SRPVerifier)); + ver = (struct SRPVerifier *)malloc(sizeof(struct SRPVerifier)); if (!ver) goto cleanup_and_exit; - ver->username = (char *)srp_alloc(ulen); + ver->username = (char *)malloc(ulen); ver->hash_alg = alg; ver->ng = ng; if (!ver->username) { - srp_free(ver); + free(ver); ver = 0; goto cleanup_and_exit; } @@ -680,7 +666,7 @@ struct SRPVerifier *srp_verifier_new(SRP_HashAlgorithm alg, } *len_B = mpz_num_bytes(B); - *bytes_B = (unsigned char *)srp_alloc(*len_B); + *bytes_B = (unsigned char *)malloc(*len_B); if (!*bytes_B) { *len_B = 0; @@ -691,7 +677,7 @@ struct SRPVerifier *srp_verifier_new(SRP_HashAlgorithm alg, ver->bytes_B = *bytes_B; } else { - srp_free(ver); + free(ver); ver = 0; } @@ -708,8 +694,8 @@ cleanup_and_exit: mpz_clear(tmp3); return ver; ver_cleanup_and_exit: - srp_free(ver->username); - srp_free(ver); + free(ver->username); + free(ver); ver = 0; goto cleanup_and_exit; } @@ -718,10 +704,10 @@ void srp_verifier_delete(struct SRPVerifier *ver) { if (ver) { delete_ng(ver->ng); - srp_free(ver->username); - srp_free(ver->bytes_B); + free(ver->username); + free(ver->bytes_B); memset(ver, 0, sizeof(*ver)); - srp_free(ver); + free(ver); } } @@ -765,7 +751,7 @@ struct SRPUser *srp_user_new(SRP_HashAlgorithm alg, SRP_NGType ng_type, const unsigned char *bytes_password, size_t len_password, const char *n_hex, const char *g_hex) { - struct SRPUser *usr = (struct SRPUser *)srp_alloc(sizeof(struct SRPUser)); + struct SRPUser *usr = (struct SRPUser *)malloc(sizeof(struct SRPUser)); size_t ulen = strlen(username) + 1; size_t uvlen = strlen(username_for_verifier) + 1; @@ -780,9 +766,9 @@ struct SRPUser *srp_user_new(SRP_HashAlgorithm alg, SRP_NGType ng_type, if (!usr->ng) goto err_exit; - usr->username = (char *)srp_alloc(ulen); - usr->username_verifier = (char *)srp_alloc(uvlen); - usr->password = (unsigned char *)srp_alloc(len_password); + usr->username = (char *)malloc(ulen); + usr->username_verifier = (char *)malloc(uvlen); + usr->password = (unsigned char *)malloc(len_password); usr->password_len = len_password; if (!usr->username || !usr->password || !usr->username_verifier) goto err_exit; @@ -803,13 +789,13 @@ err_exit: mpz_clear(usr->A); mpz_clear(usr->S); delete_ng(usr->ng); - srp_free(usr->username); - srp_free(usr->username_verifier); + free(usr->username); + free(usr->username_verifier); if (usr->password) { memset(usr->password, 0, usr->password_len); - srp_free(usr->password); + free(usr->password); } - srp_free(usr); + free(usr); } return 0; @@ -826,14 +812,14 @@ void srp_user_delete(struct SRPUser *usr) memset(usr->password, 0, usr->password_len); - srp_free(usr->username); - srp_free(usr->username_verifier); - srp_free(usr->password); + free(usr->username); + free(usr->username_verifier); + free(usr->password); - if (usr->bytes_A) srp_free(usr->bytes_A); + if (usr->bytes_A) free(usr->bytes_A); memset(usr, 0, sizeof(*usr)); - srp_free(usr); + free(usr); } } @@ -872,7 +858,7 @@ SRP_Result srp_user_start_authentication(struct SRPUser *usr, char **username, mpz_powm(usr->A, usr->ng->g, usr->a, usr->ng->N); *len_A = mpz_num_bytes(usr->A); - *bytes_A = (unsigned char *)srp_alloc(*len_A); + *bytes_A = (unsigned char *)malloc(*len_A); if (!*bytes_A) goto error_and_exit; diff --git a/src/util/srp.h b/src/util/srp.h index ac66dc936..fc4d2dc89 100644 --- a/src/util/srp.h +++ b/src/util/srp.h @@ -79,15 +79,6 @@ typedef enum { SRP_OK, } SRP_Result; -/* Sets the memory functions used by srp. - * Note: this doesn't set the memory functions used by gmp, - * but it is supported to have different functions for srp and gmp. - * Don't call this after you have already allocated srp structures. - */ -void srp_set_memory_functions( - void *(*new_srp_alloc) (size_t), - void *(*new_srp_realloc) (void *, size_t), - void (*new_srp_free) (void *)); /* Out: bytes_v, len_v * From 041d67cecad21b8cea26b74d8d0faaf0f58b138b Mon Sep 17 00:00:00 2001 From: grorp Date: Fri, 6 Sep 2024 12:11:03 +0200 Subject: [PATCH 086/200] Improve formspec scaling (#14840) --- builtin/fstk/buttonbar.lua | 7 +- builtin/fstk/tabview.lua | 22 +++- builtin/mainmenu/init.lua | 9 +- builtin/mainmenu/tab_local.lua | 10 +- doc/lua_api.md | 4 +- doc/menu_lua_api.md | 4 +- games/devtest/mods/testfullscreenfs/init.lua | 59 ++++++++-- src/clientdynamicinfo.cpp | 20 ++-- src/clientdynamicinfo.h | 2 +- src/gui/guiFormSpecMenu.cpp | 118 +++++++++++-------- src/gui/guiFormSpecMenu.h | 8 ++ 11 files changed, 176 insertions(+), 87 deletions(-) diff --git a/builtin/fstk/buttonbar.lua b/builtin/fstk/buttonbar.lua index 64ac37f03..e53814751 100644 --- a/builtin/fstk/buttonbar.lua +++ b/builtin/fstk/buttonbar.lua @@ -28,10 +28,8 @@ local function buttonbar_formspec(self) end local formspec = { - "style_type[box;noclip=true]", string.format("box[%f,%f;%f,%f;%s]", self.pos.x, self.pos.y, self.size.x, self.size.y, self.bgcolor), - "style_type[box;noclip=false]", } local btn_size = self.size.y - 2*BASE_SPACING @@ -71,7 +69,7 @@ local function buttonbar_formspec(self) y = self.pos.y + BASE_SPACING, } - table.insert(formspec, string.format("image_button[%f,%f;%f,%f;%s;%s;%s;true;false]tooltip[%s;%s]", + table.insert(formspec, string.format("image_button[%f,%f;%f,%f;%s;%s;%s;false;false]tooltip[%s;%s]", btn_pos.x, btn_pos.y, btn_size, btn_size, btn.image, btn.name, btn.caption, btn.name, btn.tooltip)) end @@ -86,9 +84,6 @@ local function buttonbar_formspec(self) y = self.pos.y + BASE_SPACING, } - table.insert(formspec, string.format("style[%s,%s;noclip=true]", - self.btn_prev_name, self.btn_next_name)) - table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;<]", btn_prev_pos.x, btn_prev_pos.y, get_scroll_btn_width(), btn_size, self.btn_prev_name)) diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua index f09c4df2d..9f8889143 100644 --- a/builtin/fstk/tabview.lua +++ b/builtin/fstk/tabview.lua @@ -66,11 +66,22 @@ local function get_formspec(self) local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize) - local tsize = tab.tabsize or { width = self.width, height = self.height } + local ENABLE_TOUCH = core.settings:get_bool("enable_touch") + + local orig_tsize = tab.tabsize or { width = self.width, height = self.height } + local tsize = { width = orig_tsize.width, height = orig_tsize.height } + tsize.height = tsize.height + + TABHEADER_H -- tabheader included in formspec size + + (ENABLE_TOUCH and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP) + + GAMEBAR_H -- gamebar included in formspec size + if self.parent == nil and not prepend then prepend = string.format("size[%f,%f,%s]", tsize.width, tsize.height, dump(self.fixed_size)) + local anchor_pos = TABHEADER_H + orig_tsize.height / 2 + prepend = prepend .. ("anchor[0.5,%f]"):format(anchor_pos / tsize.height) + if tab.formspec_version then prepend = ("formspec_version[%d]"):format(tab.formspec_version) .. prepend end @@ -78,12 +89,15 @@ local function get_formspec(self) local end_button_size = 0.75 - local tab_header_size = { width = tsize.width, height = 0.85 } + local tab_header_size = { width = tsize.width, height = TABHEADER_H } if self.end_button then tab_header_size.width = tab_header_size.width - end_button_size - 0.1 end - local formspec = (prepend or "") .. self:tab_header(tab_header_size) .. content + local formspec = (prepend or "") + formspec = formspec .. ("bgcolor[;neither]container[0,%f]box[0,0;%f,%f;#0000008C]"):format( + TABHEADER_H, orig_tsize.width, orig_tsize.height) + formspec = formspec .. self:tab_header(tab_header_size) .. content if self.end_button then formspec = formspec .. @@ -98,6 +112,8 @@ local function get_formspec(self) self.end_button.name) end + formspec = formspec .. "container_end[]" + return formspec end diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index 41885e298..dd35334c2 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -23,6 +23,13 @@ mt_color_dark_green = "#25C191" mt_color_orange = "#FF8800" mt_color_red = "#FF3300" +MAIN_TAB_W = 15.5 +MAIN_TAB_H = 7.1 +TABHEADER_H = 0.85 +GAMEBAR_H = 1.25 +GAMEBAR_OFFSET_DESKTOP = 0.375 +GAMEBAR_OFFSET_TOUCH = 0.15 + local menupath = core.get_mainmenu_path() local basepath = core.get_builtin_path() defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. @@ -89,7 +96,7 @@ local function init_globals() mm_game_theme.set_engine() -- This is just a fallback. -- Create main tabview - local tv_main = tabview_create("maintab", {x = 15.5, y = 7.1}, {x = 0, y = 0}) + local tv_main = tabview_create("maintab", {x = MAIN_TAB_W, y = MAIN_TAB_H}, {x = 0, y = 0}) tv_main:set_autosave_tab(true) tv_main:add(tabs.local_game) diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index 7f46be213..f0a7255d7 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -92,10 +92,16 @@ function singleplayer_refresh_gamebar() end end + local ENABLE_TOUCH = core.settings:get_bool("enable_touch") + + local gamebar_pos_y = MAIN_TAB_H + + TABHEADER_H -- tabheader included in formspec size + + (ENABLE_TOUCH and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP) + local btnbar = buttonbar_create( "game_button_bar", - core.settings:get_bool("touch_gui") and {x = 0, y = 7.25} or {x = 0, y = 7.475}, - {x = 15.5, y = 1.25}, + {x = 0, y = gamebar_pos_y}, + {x = MAIN_TAB_W, y = GAMEBAR_H}, "#000000", game_buttonbar_button_handler) diff --git a/doc/lua_api.md b/doc/lua_api.md index 6989f3483..ea728cfbe 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5580,8 +5580,8 @@ Utilities }, -- Estimated maximum formspec size before Minetest will start shrinking the - -- formspec to fit. For a fullscreen formspec, use a size 10-20% larger than - -- this and `padding[-0.01,-0.01]`. + -- formspec to fit. For a fullscreen formspec, use this formspec size and + -- `padding[0,0]`. `bgcolor[;true]` is also recommended. max_formspec_size = { x = 20, y = 11.25 diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index df14b859d..be63af904 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -253,8 +253,8 @@ GUI }, -- Estimated maximum formspec size before Minetest will start shrinking the - -- formspec to fit. For a fullscreen formspec, use a size 10-20% larger than - -- this and `padding[-0.01,-0.01]`. + -- formspec to fit. For a fullscreen formspec, use this formspec size and + -- `padding[0,0]`. `bgcolor[;true]` is also recommended. max_formspec_size = { x = 20, y = 11.25 diff --git a/games/devtest/mods/testfullscreenfs/init.lua b/games/devtest/mods/testfullscreenfs/init.lua index e1af3ae33..7abc7f817 100644 --- a/games/devtest/mods/testfullscreenfs/init.lua +++ b/games/devtest/mods/testfullscreenfs/init.lua @@ -1,18 +1,30 @@ -local function show_fullscreen_fs(name) - local window = minetest.get_player_window_information(name) - if not window then - return false, "Unable to get window info" - end +local function window_info_equal(a, b) + return + -- size + a.size.x == b.size.x and a.size.y == b.size.y and + -- real_gui_scaling, real_hud_scaling + a.real_gui_scaling == b.real_gui_scaling and + a.real_hud_scaling == b.real_hud_scaling and + -- max_formspec_size + a.max_formspec_size.x == b.max_formspec_size.x and + a.max_formspec_size.y == b.max_formspec_size.y and + -- touch_controls + a.touch_controls == b.touch_controls +end +local last_window_info = {} + +local function show_fullscreen_fs(name, window) print(dump(window)) - local size = { x = window.max_formspec_size.x * 1.1, y = window.max_formspec_size.y * 1.1 } + local size = window.max_formspec_size local touch_text = window.touch_controls and "Touch controls enabled" or "Touch controls disabled" local fs = { "formspec_version[4]", ("size[%f,%f]"):format(size.x, size.y), - "padding[-0.01,-0.01]", + "padding[0,0]", + "bgcolor[;true]", ("button[%f,%f;1,1;%s;%s]"):format(0, 0, "tl", "TL"), ("button[%f,%f;1,1;%s;%s]"):format(size.x - 1, 0, "tr", "TR"), ("button[%f,%f;1,1;%s;%s]"):format(size.x - 1, size.y - 1, "br", "BR"), @@ -23,10 +35,37 @@ local function show_fullscreen_fs(name) } minetest.show_formspec(name, "testfullscreenfs:fs", table.concat(fs)) - return true, ("Calculated size of %f, %f"):format(size.x, size.y) + minetest.chat_send_player(name, ("Calculated size of %f, %f"):format(size.x, size.y)) + last_window_info[name] = window end - minetest.register_chatcommand("testfullscreenfs", { - func = show_fullscreen_fs, + func = function(name) + local window = minetest.get_player_window_information(name) + if not window then + return false, "Unable to get window info" + end + + show_fullscreen_fs(name, window) + return true + end, }) + +minetest.register_globalstep(function() + for name, last_window in pairs(last_window_info) do + local window = minetest.get_player_window_information(name) + if window and not window_info_equal(last_window, window) then + show_fullscreen_fs(name, window) + end + end +end) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname == "testfullscreenfs:fs" and fields.quit then + last_window_info[player:get_player_name()] = nil + end +end) + +minetest.register_on_leaveplayer(function(player) + last_window_info[player:get_player_name()] = nil +end) diff --git a/src/clientdynamicinfo.cpp b/src/clientdynamicinfo.cpp index c206018f3..12bc23abd 100644 --- a/src/clientdynamicinfo.cpp +++ b/src/clientdynamicinfo.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "client/renderingengine.h" +#include "gui/guiFormSpecMenu.h" #include "gui/touchcontrols.h" ClientDynamicInfo ClientDynamicInfo::getCurrent() @@ -37,19 +38,22 @@ ClientDynamicInfo ClientDynamicInfo::getCurrent() return { screen_size, real_gui_scaling, real_hud_scaling, - ClientDynamicInfo::calculateMaxFSSize(screen_size, gui_scaling), + ClientDynamicInfo::calculateMaxFSSize(screen_size, density, gui_scaling), touch_controls }; } -v2f32 ClientDynamicInfo::calculateMaxFSSize(v2u32 render_target_size, f32 gui_scaling) +v2f32 ClientDynamicInfo::calculateMaxFSSize(v2u32 render_target_size, f32 density, f32 gui_scaling) { - f32 factor = (g_settings->getBool("touch_gui") ? 10 : 15) / gui_scaling; - f32 ratio = (f32)render_target_size.X / (f32)render_target_size.Y; - if (ratio < 1) - return { factor, factor / ratio }; - else - return { factor * ratio, factor }; + // must stay in sync with GUIFormSpecMenu::calculateImgsize + + const double screen_dpi = density * 96; + + // assume padding[0,0] since max_formspec_size is used for fullscreen formspecs + double prefer_imgsize = GUIFormSpecMenu::getImgsize(render_target_size, + screen_dpi, gui_scaling); + return v2f32(render_target_size.X / prefer_imgsize, + render_target_size.Y / prefer_imgsize); } #endif diff --git a/src/clientdynamicinfo.h b/src/clientdynamicinfo.h index 39faeeecc..c43fcb8d8 100644 --- a/src/clientdynamicinfo.h +++ b/src/clientdynamicinfo.h @@ -42,6 +42,6 @@ public: static ClientDynamicInfo getCurrent(); private: - static v2f32 calculateMaxFSSize(v2u32 render_target_size, f32 gui_scaling); + static v2f32 calculateMaxFSSize(v2u32 render_target_size, f32 density, f32 gui_scaling); #endif }; diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 8b572276c..0371c26f3 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3133,58 +3133,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) offset = v2s32(0,0); } - const double gui_scaling = g_settings->getFloat("gui_scaling", 0.5f, 42.0f); - const double screen_dpi = RenderingEngine::getDisplayDensity() * 96; - - double use_imgsize; - if (m_lock) { - // In fixed-size mode, inventory image size - // is 0.53 inch multiplied by the gui_scaling - // config parameter. This magic size is chosen - // to make the main menu (15.5 inventory images - // wide, including border) just fit into the - // default window (800 pixels wide) at 96 DPI - // and default scaling (1.00). - use_imgsize = 0.5555 * screen_dpi * gui_scaling; - } else { - // Variables for the maximum imgsize that can fit in the screen. - double fitx_imgsize; - double fity_imgsize; - - v2f padded_screensize( - mydata.screensize.X * (1.0f - mydata.padding.X * 2.0f), - mydata.screensize.Y * (1.0f - mydata.padding.Y * 2.0f) - ); - - if (mydata.real_coordinates) { - fitx_imgsize = padded_screensize.X / mydata.invsize.X; - fity_imgsize = padded_screensize.Y / mydata.invsize.Y; - } else { - // The maximum imgsize in the old coordinate system also needs to - // factor in padding and spacing along with 0.1 inventory slot spare - // and help text space, hence the magic numbers. - fitx_imgsize = padded_screensize.X / - ((5.0 / 4.0) * (0.5 + mydata.invsize.X)); - fity_imgsize = padded_screensize.Y / - ((15.0 / 13.0) * (0.85 + mydata.invsize.Y)); - } - - s32 min_screen_dim = std::min(padded_screensize.X, padded_screensize.Y); - - double prefer_imgsize; - if (g_settings->getBool("touch_gui")) { - // The preferred imgsize should be larger to accommodate the - // smaller screensize. - prefer_imgsize = min_screen_dim / 10 * gui_scaling; - } else { - // Desktop computers have more space, so try to fit 15 coordinates. - prefer_imgsize = min_screen_dim / 15 * gui_scaling; - } - // Try to use the preferred imgsize, but if that's bigger than the maximum - // size, use the maximum size. - use_imgsize = std::min(prefer_imgsize, - std::min(fitx_imgsize, fity_imgsize)); - } + double use_imgsize = calculateImgsize(mydata); // Everything else is scaled in proportion to the // inventory image size. The inventory slot spacing @@ -5072,3 +5021,68 @@ std::array GUIFormSpecMenu::getStyleForElement return ret; } + +double GUIFormSpecMenu::getFixedImgsize(double screen_dpi, double gui_scaling) +{ + // In fixed-size mode, inventory image size + // is 0.53 inch multiplied by the gui_scaling + // config parameter. This magic size is chosen + // to make the main menu (15.5 inventory images + // wide, including border) just fit into the + // default window (800 pixels wide) at 96 DPI + // and default scaling (1.00). + return 0.5555 * screen_dpi * gui_scaling; +} + +double GUIFormSpecMenu::getImgsize(v2u32 avail_screensize, double screen_dpi, double gui_scaling) +{ + double fixed_imgsize = getFixedImgsize(screen_dpi, gui_scaling); + + s32 min_screen_dim = std::min(avail_screensize.X, avail_screensize.Y); + double prefer_imgsize = min_screen_dim / 15 * gui_scaling; + // Use the available space more effectively on small windows/screens. + // This is especially important for mobile platforms. + prefer_imgsize = std::max(prefer_imgsize, fixed_imgsize); + return prefer_imgsize; +} + +double GUIFormSpecMenu::calculateImgsize(const parserData &data) +{ + // must stay in sync with ClientDynamicInfo::calculateMaxFSSize + + const double screen_dpi = RenderingEngine::getDisplayDensity() * 96; + const double gui_scaling = g_settings->getFloat("gui_scaling", 0.5f, 42.0f); + + // Fixed-size mode + if (m_lock) + return getFixedImgsize(screen_dpi, gui_scaling); + + // Variables for the maximum imgsize that can fit in the screen. + double fitx_imgsize; + double fity_imgsize; + + v2f padded_screensize( + data.screensize.X * (1.0f - data.padding.X * 2.0f), + data.screensize.Y * (1.0f - data.padding.Y * 2.0f) + ); + + if (data.real_coordinates) { + fitx_imgsize = padded_screensize.X / data.invsize.X; + fity_imgsize = padded_screensize.Y / data.invsize.Y; + } else { + // The maximum imgsize in the old coordinate system also needs to + // factor in padding and spacing along with 0.1 inventory slot spare + // and help text space, hence the magic numbers. + fitx_imgsize = padded_screensize.X / + ((5.0 / 4.0) * (0.5 + data.invsize.X)); + fity_imgsize = padded_screensize.Y / + ((15.0 / 13.0) * (0.85 + data.invsize.Y)); + } + + double prefer_imgsize = getImgsize(v2u32(padded_screensize.X, padded_screensize.Y), + screen_dpi, gui_scaling); + + // Try to use the preferred imgsize, but if that's bigger than the maximum + // size, use the maximum size. + return std::min(prefer_imgsize, std::min(fitx_imgsize, fity_imgsize)); +} diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 12add12e6..7c4be4301 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -296,6 +296,11 @@ public: void getAndroidUIInput(); #endif + // Returns the fixed formspec coordinate size for the given parameters. + static double getFixedImgsize(double screen_dpi, double gui_scaling); + // Returns the preferred non-fixed formspec coordinate size for the given parameters. + static double getImgsize(v2u32 avail_screensize, double screen_dpi, double gui_scaling); + protected: v2s32 getBasePos() const { @@ -514,6 +519,9 @@ private: // used by getAbsoluteRect s32 m_tabheader_upper_edge = 0; + + // Determines the size (in pixels) of formspec coordinate units. + double calculateImgsize(const parserData &data); }; class FormspecFormSource: public IFormSource From 9e5d6bc16290aadc30e46e21255495cab800cf34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:11:16 +0200 Subject: [PATCH 087/200] Fix upright sprite entities not animating --- src/client/content_cao.cpp | 10 ++++++++++ src/client/mesh.cpp | 10 +--------- src/client/mesh.h | 7 ------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 563fe0abd..72f24dfca 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1264,6 +1264,16 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) } } +static void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count) +{ + assert(buf->getVertexType() == video::EVT_STANDARD); + assert(buf->getVertexCount() == count); + auto *vertices = static_cast(buf->getVertices()); + for (u32 i = 0; i < count; i++) + vertices[i].TCoords = uv[i]; + buf->setDirty(scene::EBT_VERTEX); +} + void GenericCAO::updateTexturePos() { if(m_spritenode) diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 711712c33..6196e04d4 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "mesh.h" +#include "S3DVertex.h" #include "debug.h" #include "log.h" #include @@ -197,15 +198,6 @@ void setMeshColor(scene::IMesh *mesh, const video::SColor &color) setMeshBufferColor(mesh->getMeshBuffer(j), color); } -void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count) -{ - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - assert(buf->getVertexCount() >= count); - u8 *vertices = (u8 *) buf->getVertices(); - for (u32 i = 0; i < count; i++) - ((video::S3DVertex*) (vertices + i * stride))->TCoords = uv[i]; -} - template static void applyToMesh(scene::IMesh *mesh, const F &fn) { diff --git a/src/client/mesh.h b/src/client/mesh.h index 106787af3..35d3886aa 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -59,13 +59,6 @@ void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color); */ void setMeshColor(scene::IMesh *mesh, const video::SColor &color); - -/* - Sets texture coords for vertices in the mesh buffer. - `uv[]` must have `count` elements -*/ -void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count); - /* Set a constant color for an animated mesh */ From e90ef85e7d8c9497585f1c413686a9f74da565e5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 6 Sep 2024 10:32:05 +0200 Subject: [PATCH 088/200] Fix texture matrix handling in our shaders --- client/shaders/object_shader/opengl_vertex.glsl | 2 +- irr/src/OpenGL/Driver.cpp | 7 +++++-- src/client/shader.cpp | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl index d5a434da5..65acba92a 100644 --- a/client/shaders/object_shader/opengl_vertex.glsl +++ b/client/shaders/object_shader/opengl_vertex.glsl @@ -91,7 +91,7 @@ float directional_ambient(vec3 normal) void main(void) { - varTexCoord = (mTexture * inTexCoord0).st; + varTexCoord = (mTexture * vec4(inTexCoord0.xy, 1.0, 1.0)).st; gl_Position = mWorldViewProj * inVertexPosition; vPosition = gl_Position.xyz; diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 46aa36d5c..3a921d104 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -1096,8 +1096,11 @@ void COpenGL3DriverBase::setMaterial(const SMaterial &material) OverrideMaterial.apply(Material); for (u32 i = 0; i < Feature.MaxTextureUnits; ++i) { - CacheHandler->getTextureCache().set(i, material.getTexture(i)); - setTransform((E_TRANSFORMATION_STATE)(ETS_TEXTURE_0 + i), material.getTextureMatrix(i)); + auto *texture = material.getTexture(i); + CacheHandler->getTextureCache().set(i, texture); + if (texture) { + setTransform((E_TRANSFORMATION_STATE)(ETS_TEXTURE_0 + i), material.getTextureMatrix(i)); + } } } diff --git a/src/client/shader.cpp b/src/client/shader.cpp index dae53ff96..242fda81c 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -249,7 +249,7 @@ public: m_world_view_proj.set(worldViewProj, services); if (driver->getDriverType() == video::EDT_OGLES2 || driver->getDriverType() == video::EDT_OPENGL3) { - core::matrix4 texture = driver->getTransform(video::ETS_TEXTURE_0); + auto &texture = driver->getTransform(video::ETS_TEXTURE_0); m_world_view.set(worldView, services); m_texture.set(texture, services); } @@ -573,6 +573,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, } else { shaders_header << "#version 100\n"; } + // cf. EVertexAttributes.h for the predefined ones vertex_header = R"( precision mediump float; @@ -582,7 +583,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, attribute highp vec4 inVertexPosition; attribute lowp vec4 inVertexColor; - attribute mediump vec4 inTexCoord0; + attribute mediump vec2 inTexCoord0; attribute mediump vec3 inVertexNormal; attribute mediump vec4 inVertexTangent; attribute mediump vec4 inVertexBinormal; From 275bef06337b48b56af111d00a0df5d00287f512 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 6 Sep 2024 11:02:26 +0200 Subject: [PATCH 089/200] Remove unused leftovers from normal mapping --- src/client/mapblock_mesh.cpp | 8 -------- src/client/texturesource.cpp | 23 ----------------------- src/client/texturesource.h | 2 -- src/client/tile.h | 4 ---- src/client/wieldmesh.cpp | 14 -------------- src/nodedef.cpp | 9 +-------- 6 files changed, 1 insertion(+), 59 deletions(-) diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 5b47a32ff..32d559149 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -746,9 +746,6 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs material.MaterialType = m_shdrsrc->getShaderInfo( p.layer.shader_id).material; p.layer.applyMaterialOptionsWithShaders(material); - if (p.layer.normal_texture) - material.setTexture(1, p.layer.normal_texture); - material.setTexture(2, p.layer.flags_texture); } else { p.layer.applyMaterialOptions(material); } @@ -858,11 +855,6 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, const FrameSpec &frame = (*tile.frames)[frameno]; buf->getMaterial().setTexture(0, frame.texture); - if (m_enable_shaders) { - if (frame.normal_texture) - buf->getMaterial().setTexture(1, frame.normal_texture); - buf->getMaterial().setTexture(2, frame.flags_texture); - } } // Day-night transition diff --git a/src/client/texturesource.cpp b/src/client/texturesource.cpp index a4222f414..f18fa6cbf 100644 --- a/src/client/texturesource.cpp +++ b/src/client/texturesource.cpp @@ -137,7 +137,6 @@ public: video::ITexture* getNormalTexture(const std::string &name); video::SColor getTextureAverageColor(const std::string &name); - video::ITexture *getShaderFlagsTexture(bool normamap_present); private: @@ -541,25 +540,3 @@ video::SColor TextureSource::getTextureAverageColor(const std::string &name) return c; } - - -video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present) -{ - std::string tname = "__shaderFlagsTexture"; - tname += normalmap_present ? "1" : "0"; - - if (isKnownSourceImage(tname)) { - return getTexture(tname); - } - - video::IVideoDriver *driver = RenderingEngine::get_video_driver(); - video::IImage *flags_image = driver->createImage( - video::ECF_A8R8G8B8, core::dimension2d(1, 1)); - sanity_check(flags_image); - video::SColor c(255, normalmap_present ? 255 : 0, 0, 0); - flags_image->setPixel(0, 0, c); - insertSourceImage(tname, flags_image); - flags_image->drop(); - return getTexture(tname); - -} diff --git a/src/client/texturesource.h b/src/client/texturesource.h index d4880ed4c..324c58e4f 100644 --- a/src/client/texturesource.h +++ b/src/client/texturesource.h @@ -71,7 +71,6 @@ public: virtual bool isKnownSourceImage(const std::string &name)=0; virtual video::ITexture* getNormalTexture(const std::string &name)=0; virtual video::SColor getTextureAverageColor(const std::string &name)=0; - virtual video::ITexture *getShaderFlagsTexture(bool normalmap_present)=0; }; class IWritableTextureSource : public ITextureSource @@ -93,7 +92,6 @@ public: virtual void rebuildImagesAndTextures()=0; virtual video::ITexture* getNormalTexture(const std::string &name)=0; virtual video::SColor getTextureAverageColor(const std::string &name)=0; - virtual video::ITexture *getShaderFlagsTexture(bool normalmap_present)=0; }; IWritableTextureSource *createTextureSource(); diff --git a/src/client/tile.h b/src/client/tile.h index d761eefdd..f41b127bf 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -62,8 +62,6 @@ struct FrameSpec u32 texture_id = 0; video::ITexture *texture = nullptr; - video::ITexture *normal_texture = nullptr; - video::ITexture *flags_texture = nullptr; }; #define MAX_TILE_LAYERS 2 @@ -114,8 +112,6 @@ struct TileLayer // Ordered for size, please do not reorder video::ITexture *texture = nullptr; - video::ITexture *normal_texture = nullptr; - video::ITexture *flags_texture = nullptr; u32 shader_id = 0; diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 66f89efb1..148130606 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -306,9 +306,6 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, }); // mipmaps cause "thin black line" artifacts material.UseMipMaps = false; - if (m_enable_shaders) { - material.setTexture(2, tsrc->getShaderFlagsTexture(false)); - } } } @@ -343,7 +340,6 @@ static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n, if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { const FrameSpec &frame = (*p.layer.frames)[0]; p.layer.texture = frame.texture; - p.layer.normal_texture = frame.normal_texture; } for (video::S3DVertex &v : p.vertices) { v.Color.setAlpha(255); @@ -772,16 +768,6 @@ void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, } else { material.setTexture(0, layer->texture); } - if (use_shaders) { - if (layer->normal_texture) { - if (layer->animation_frame_count > 1) { - const FrameSpec &animation_frame = (*layer->frames)[0]; - material.setTexture(1, animation_frame.normal_texture); - } else - material.setTexture(1, layer->normal_texture); - } - material.setTexture(2, layer->flags_texture); - } if (apply_scale && tile->world_aligned) { u32 n = buf->getVertexCount(); diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 2914cc3aa..811753c89 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -712,8 +712,6 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, if (!tile.world_aligned) layer->scale = 1; - layer->flags_texture = tsrc->getShaderFlagsTexture(layer->normal_texture ? true : false); - // Material flags layer->material_flags = 0; if (backface_culling) @@ -753,18 +751,13 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, std::ostringstream os(std::ios::binary); for (int i = 0; i < frame_count; i++) { - FrameSpec frame; - os.str(""); os << tiledef.name; tiledef.animation.getTextureModifer(os, layer->texture->getOriginalSize(), i); + FrameSpec &frame = (*layer->frames)[i]; frame.texture = tsrc->getTextureForMesh(os.str(), &frame.texture_id); - if (layer->normal_texture) - frame.normal_texture = tsrc->getNormalTexture(os.str()); - frame.flags_texture = layer->flags_texture; - (*layer->frames)[i] = frame; } } } From 3feec87d5214caad3b782fa9262495a98b3acdc5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 7 Sep 2024 13:55:33 +0200 Subject: [PATCH 090/200] Count global number of drawcalls too --- irr/include/IVideoDriver.h | 2 ++ irr/src/CNullDriver.cpp | 2 ++ src/client/game.cpp | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index b3312160c..af8d97fef 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -54,6 +54,8 @@ const c8 *const FogTypeNames[] = { }; struct SFrameStats { + //! Number of draw calls + u32 Drawcalls = 0; //! Count of primitives drawn u32 PrimitivesDrawn = 0; //! Number of hardware buffers uploaded (new or updated) diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index 6f261cef1..91f441a14 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -605,6 +605,7 @@ void CNullDriver::drawVertexPrimitiveList(const void *vertices, u32 vertexCount, { if ((iType == EIT_16BIT) && (vertexCount > 65536)) os::Printer::log("Too many vertices for 16bit index type, render artifacts may occur."); + FrameStats.Drawcalls++; FrameStats.PrimitivesDrawn += primitiveCount; } @@ -613,6 +614,7 @@ void CNullDriver::draw2DVertexPrimitiveList(const void *vertices, u32 vertexCoun { if ((iType == EIT_16BIT) && (vertexCount > 65536)) os::Printer::log("Too many vertices for 16bit index type, render artifacts may occur."); + FrameStats.Drawcalls++; FrameStats.PrimitivesDrawn += primitiveCount; } diff --git a/src/client/game.cpp b/src/client/game.cpp index 7213faafa..5855be96f 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1953,7 +1953,10 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, g_profiler->graphSet("FPS", 1.0f / dtime); auto stats2 = driver->getFrameStats(); - g_profiler->avg("Irr: primitives drawn", stats2.PrimitivesDrawn); + g_profiler->avg("Irr: drawcalls", stats2.Drawcalls); + if (stats2.Drawcalls > 0) + g_profiler->avg("Irr: primitives per drawcall", + stats2.PrimitivesDrawn / float(stats2.Drawcalls)); g_profiler->avg("Irr: buffers uploaded", stats2.HWBuffersUploaded); g_profiler->avg("Irr: buffers uploaded (bytes)", stats2.HWBuffersUploadedSize); } From c8ebc2e5d00a4ba7d502cac38104c1550d9577a6 Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Fri, 6 Sep 2024 21:11:13 +0200 Subject: [PATCH 091/200] Delete Irrlicht CGUISkin --- irr/src/CGUISkin.cpp | 891 ------------------------------------------- irr/src/CGUISkin.h | 233 ----------- 2 files changed, 1124 deletions(-) delete mode 100644 irr/src/CGUISkin.cpp delete mode 100644 irr/src/CGUISkin.h diff --git a/irr/src/CGUISkin.cpp b/irr/src/CGUISkin.cpp deleted file mode 100644 index 84ceaeabf..000000000 --- a/irr/src/CGUISkin.cpp +++ /dev/null @@ -1,891 +0,0 @@ -// Copyright (C) 2002-2012 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#include "CGUISkin.h" - -#include "IGUIFont.h" -#include "IGUISpriteBank.h" -#include "IGUIElement.h" -#include "IVideoDriver.h" -#include "IAttributes.h" - -namespace irr -{ -namespace gui -{ - -CGUISkin::CGUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver *driver) : - SpriteBank(0), Driver(driver), Type(type) -{ -#ifdef _DEBUG - setDebugName("CGUISkin"); -#endif - - if ((Type == EGST_WINDOWS_CLASSIC) || (Type == EGST_WINDOWS_METALLIC)) { - Colors[EGDC_3D_DARK_SHADOW] = video::SColor(101, 50, 50, 50); - Colors[EGDC_3D_SHADOW] = video::SColor(101, 130, 130, 130); - Colors[EGDC_3D_FACE] = video::SColor(101, 210, 210, 210); - Colors[EGDC_3D_HIGH_LIGHT] = video::SColor(101, 255, 255, 255); - Colors[EGDC_3D_LIGHT] = video::SColor(101, 210, 210, 210); - Colors[EGDC_ACTIVE_BORDER] = video::SColor(101, 16, 14, 115); - Colors[EGDC_ACTIVE_CAPTION] = video::SColor(255, 255, 255, 255); - Colors[EGDC_APP_WORKSPACE] = video::SColor(101, 100, 100, 100); - Colors[EGDC_BUTTON_TEXT] = video::SColor(240, 10, 10, 10); - Colors[EGDC_GRAY_TEXT] = video::SColor(240, 130, 130, 130); - Colors[EGDC_HIGH_LIGHT] = video::SColor(101, 8, 36, 107); - Colors[EGDC_HIGH_LIGHT_TEXT] = video::SColor(240, 255, 255, 255); - Colors[EGDC_INACTIVE_BORDER] = video::SColor(101, 165, 165, 165); - Colors[EGDC_INACTIVE_CAPTION] = video::SColor(255, 30, 30, 30); - Colors[EGDC_TOOLTIP] = video::SColor(200, 0, 0, 0); - Colors[EGDC_TOOLTIP_BACKGROUND] = video::SColor(200, 255, 255, 225); - Colors[EGDC_SCROLLBAR] = video::SColor(101, 230, 230, 230); - Colors[EGDC_WINDOW] = video::SColor(101, 255, 255, 255); - Colors[EGDC_WINDOW_SYMBOL] = video::SColor(200, 10, 10, 10); - Colors[EGDC_ICON] = video::SColor(200, 255, 255, 255); - Colors[EGDC_ICON_HIGH_LIGHT] = video::SColor(200, 8, 36, 107); - Colors[EGDC_GRAY_WINDOW_SYMBOL] = video::SColor(240, 100, 100, 100); - Colors[EGDC_EDITABLE] = video::SColor(255, 255, 255, 255); - Colors[EGDC_GRAY_EDITABLE] = video::SColor(255, 120, 120, 120); - Colors[EGDC_FOCUSED_EDITABLE] = video::SColor(255, 240, 240, 255); - - Sizes[EGDS_SCROLLBAR_SIZE] = 14; - Sizes[EGDS_MENU_HEIGHT] = 30; - Sizes[EGDS_WINDOW_BUTTON_WIDTH] = 15; - Sizes[EGDS_CHECK_BOX_WIDTH] = 18; - Sizes[EGDS_MESSAGE_BOX_WIDTH] = 500; - Sizes[EGDS_MESSAGE_BOX_HEIGHT] = 200; - Sizes[EGDS_BUTTON_WIDTH] = 80; - Sizes[EGDS_BUTTON_HEIGHT] = 30; - - Sizes[EGDS_TEXT_DISTANCE_X] = 2; - Sizes[EGDS_TEXT_DISTANCE_Y] = 0; - - Sizes[EGDS_TITLEBARTEXT_DISTANCE_X] = 2; - Sizes[EGDS_TITLEBARTEXT_DISTANCE_Y] = 0; - } else { - // 0x80a6a8af - Colors[EGDC_3D_DARK_SHADOW] = 0x60767982; - // Colors[EGDC_3D_FACE] = 0xc0c9ccd4; // tab background - Colors[EGDC_3D_FACE] = 0xc0cbd2d9; // tab background - Colors[EGDC_3D_SHADOW] = 0x50e4e8f1; // tab background, and left-top highlight - Colors[EGDC_3D_HIGH_LIGHT] = 0x40c7ccdc; - Colors[EGDC_3D_LIGHT] = 0x802e313a; - Colors[EGDC_ACTIVE_BORDER] = 0x80404040; // window title - Colors[EGDC_ACTIVE_CAPTION] = 0xffd0d0d0; - Colors[EGDC_APP_WORKSPACE] = 0xc0646464; // unused - Colors[EGDC_BUTTON_TEXT] = 0xd0161616; - Colors[EGDC_GRAY_TEXT] = 0x3c141414; - Colors[EGDC_HIGH_LIGHT] = 0x6c606060; - Colors[EGDC_HIGH_LIGHT_TEXT] = 0xd0e0e0e0; - Colors[EGDC_INACTIVE_BORDER] = 0xf0a5a5a5; - Colors[EGDC_INACTIVE_CAPTION] = 0xffd2d2d2; - Colors[EGDC_TOOLTIP] = 0xf00f2033; - Colors[EGDC_TOOLTIP_BACKGROUND] = 0xc0cbd2d9; - Colors[EGDC_SCROLLBAR] = 0xf0e0e0e0; - Colors[EGDC_WINDOW] = 0xf0f0f0f0; - Colors[EGDC_WINDOW_SYMBOL] = 0xd0161616; - Colors[EGDC_ICON] = 0xd0161616; - Colors[EGDC_ICON_HIGH_LIGHT] = 0xd0606060; - Colors[EGDC_GRAY_WINDOW_SYMBOL] = 0x3c101010; - Colors[EGDC_EDITABLE] = 0xf0ffffff; - Colors[EGDC_GRAY_EDITABLE] = 0xf0cccccc; - Colors[EGDC_FOCUSED_EDITABLE] = 0xf0fffff0; - - Sizes[EGDS_SCROLLBAR_SIZE] = 14; - Sizes[EGDS_MENU_HEIGHT] = 48; - Sizes[EGDS_WINDOW_BUTTON_WIDTH] = 15; - Sizes[EGDS_CHECK_BOX_WIDTH] = 18; - Sizes[EGDS_MESSAGE_BOX_WIDTH] = 500; - Sizes[EGDS_MESSAGE_BOX_HEIGHT] = 200; - Sizes[EGDS_BUTTON_WIDTH] = 80; - Sizes[EGDS_BUTTON_HEIGHT] = 30; - - Sizes[EGDS_TEXT_DISTANCE_X] = 3; - Sizes[EGDS_TEXT_DISTANCE_Y] = 2; - - Sizes[EGDS_TITLEBARTEXT_DISTANCE_X] = 3; - Sizes[EGDS_TITLEBARTEXT_DISTANCE_Y] = 2; - } - - Sizes[EGDS_MESSAGE_BOX_GAP_SPACE] = 15; - Sizes[EGDS_MESSAGE_BOX_MIN_TEXT_WIDTH] = 0; - Sizes[EGDS_MESSAGE_BOX_MAX_TEXT_WIDTH] = 500; - Sizes[EGDS_MESSAGE_BOX_MIN_TEXT_HEIGHT] = 0; - Sizes[EGDS_MESSAGE_BOX_MAX_TEXT_HEIGHT] = 99999; - - Sizes[EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X] = 1; - Sizes[EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y] = 1; - Sizes[EGDS_BUTTON_PRESSED_TEXT_OFFSET_X] = 0; - Sizes[EGDS_BUTTON_PRESSED_TEXT_OFFSET_Y] = 2; - Sizes[EGDS_BUTTON_PRESSED_SPRITE_OFFSET_X] = 0; - Sizes[EGDS_BUTTON_PRESSED_SPRITE_OFFSET_Y] = 0; - - Texts[EGDT_MSG_BOX_OK] = L"OK"; - Texts[EGDT_MSG_BOX_CANCEL] = L"Cancel"; - Texts[EGDT_MSG_BOX_YES] = L"Yes"; - Texts[EGDT_MSG_BOX_NO] = L"No"; - Texts[EGDT_WINDOW_CLOSE] = L"Close"; - Texts[EGDT_WINDOW_RESTORE] = L"Restore"; - Texts[EGDT_WINDOW_MINIMIZE] = L"Minimize"; - Texts[EGDT_WINDOW_MAXIMIZE] = L"Maximize"; - - Icons[EGDI_WINDOW_MAXIMIZE] = 225; - Icons[EGDI_WINDOW_RESTORE] = 226; - Icons[EGDI_WINDOW_CLOSE] = 227; - Icons[EGDI_WINDOW_MINIMIZE] = 228; - Icons[EGDI_CURSOR_UP] = 229; - Icons[EGDI_CURSOR_DOWN] = 230; - Icons[EGDI_CURSOR_LEFT] = 231; - Icons[EGDI_CURSOR_RIGHT] = 232; - Icons[EGDI_MENU_MORE] = 232; - Icons[EGDI_CHECK_BOX_CHECKED] = 233; - Icons[EGDI_DROP_DOWN] = 234; - Icons[EGDI_SMALL_CURSOR_UP] = 235; - Icons[EGDI_SMALL_CURSOR_DOWN] = 236; - Icons[EGDI_RADIO_BUTTON_CHECKED] = 237; - Icons[EGDI_MORE_LEFT] = 238; - Icons[EGDI_MORE_RIGHT] = 239; - Icons[EGDI_MORE_UP] = 240; - Icons[EGDI_MORE_DOWN] = 241; - Icons[EGDI_WINDOW_RESIZE] = 242; - Icons[EGDI_EXPAND] = 243; - Icons[EGDI_COLLAPSE] = 244; - - Icons[EGDI_FILE] = 245; - Icons[EGDI_DIRECTORY] = 246; - - for (u32 i = 0; i < EGDF_COUNT; ++i) - Fonts[i] = 0; - - UseGradient = (Type == EGST_WINDOWS_METALLIC) || (Type == EGST_BURNING_SKIN); -} - -//! destructor -CGUISkin::~CGUISkin() -{ - for (u32 i = 0; i < EGDF_COUNT; ++i) { - if (Fonts[i]) - Fonts[i]->drop(); - } - - if (SpriteBank) - SpriteBank->drop(); -} - -//! returns default color -video::SColor CGUISkin::getColor(EGUI_DEFAULT_COLOR color) const -{ - if ((u32)color < EGDC_COUNT) - return Colors[color]; - else - return video::SColor(); -} - -//! sets a default color -void CGUISkin::setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor) -{ - if ((u32)which < EGDC_COUNT) - Colors[which] = newColor; -} - -//! returns size for the given size type -s32 CGUISkin::getSize(EGUI_DEFAULT_SIZE size) const -{ - if ((u32)size < EGDS_COUNT) - return Sizes[size]; - else - return 0; -} - -//! sets a default size -void CGUISkin::setSize(EGUI_DEFAULT_SIZE which, s32 size) -{ - if ((u32)which < EGDS_COUNT) - Sizes[which] = size; -} - -//! returns the default font -IGUIFont *CGUISkin::getFont(EGUI_DEFAULT_FONT which) const -{ - if (((u32)which < EGDF_COUNT) && Fonts[which]) - return Fonts[which]; - else - return Fonts[EGDF_DEFAULT]; -} - -//! sets a default font -void CGUISkin::setFont(IGUIFont *font, EGUI_DEFAULT_FONT which) -{ - if ((u32)which >= EGDF_COUNT) - return; - - if (font) { - font->grab(); - if (Fonts[which]) - Fonts[which]->drop(); - - Fonts[which] = font; - } -} - -//! gets the sprite bank stored -IGUISpriteBank *CGUISkin::getSpriteBank() const -{ - return SpriteBank; -} - -//! set a new sprite bank or remove one by passing 0 -void CGUISkin::setSpriteBank(IGUISpriteBank *bank) -{ - if (bank) - bank->grab(); - - if (SpriteBank) - SpriteBank->drop(); - - SpriteBank = bank; -} - -//! Returns a default icon -u32 CGUISkin::getIcon(EGUI_DEFAULT_ICON icon) const -{ - if ((u32)icon < EGDI_COUNT) - return Icons[icon]; - else - return 0; -} - -//! Sets a default icon -void CGUISkin::setIcon(EGUI_DEFAULT_ICON icon, u32 index) -{ - if ((u32)icon < EGDI_COUNT) - Icons[icon] = index; -} - -//! Returns a default text. For example for Message box button captions: -//! "OK", "Cancel", "Yes", "No" and so on. -const wchar_t *CGUISkin::getDefaultText(EGUI_DEFAULT_TEXT text) const -{ - if ((u32)text < EGDT_COUNT) - return Texts[text].c_str(); - else - return Texts[0].c_str(); -} - -//! Sets a default text. For example for Message box button captions: -//! "OK", "Cancel", "Yes", "No" and so on. -void CGUISkin::setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t *newText) -{ - if ((u32)which < EGDT_COUNT) - Texts[which] = newText; -} - -//! draws a standard 3d button pane -/** Used for drawing for example buttons in normal state. -It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and -EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. -\param rect: Defining area where to draw. -\param clip: Clip area. -\param element: Pointer to the element which wishes to draw this. This parameter -is usually not used by ISkin, but can be used for example by more complex -implementations to find out how to draw the part exactly. */ -void CGUISkin::draw3DButtonPaneStandard(IGUIElement *element, - const core::rect &r, - const core::rect *clip) -{ - if (!Driver) - return; - - core::rect rect = r; - - if (Type == EGST_BURNING_SKIN) { - rect.UpperLeftCorner.X -= 1; - rect.UpperLeftCorner.Y -= 1; - rect.LowerRightCorner.X += 1; - rect.LowerRightCorner.Y += 1; - draw3DSunkenPane(element, - getColor(EGDC_WINDOW).getInterpolated(0xFFFFFFFF, 0.9f), false, true, rect, clip); - return; - } - - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), rect, clip); - - rect.LowerRightCorner.X -= 1; - rect.LowerRightCorner.Y -= 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); - - rect.UpperLeftCorner.X += 1; - rect.UpperLeftCorner.Y += 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); - - rect.LowerRightCorner.X -= 1; - rect.LowerRightCorner.Y -= 1; - - if (!UseGradient) { - Driver->draw2DRectangle(getColor(EGDC_3D_FACE), rect, clip); - } else { - const video::SColor c1 = getColor(EGDC_3D_FACE); - const video::SColor c2 = c1.getInterpolated(getColor(EGDC_3D_DARK_SHADOW), 0.4f); - Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); - } -} - -//! draws a pressed 3d button pane -/** Used for drawing for example buttons in pressed state. -It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and -EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. -\param rect: Defining area where to draw. -\param clip: Clip area. -\param element: Pointer to the element which wishes to draw this. This parameter -is usually not used by ISkin, but can be used for example by more complex -implementations to find out how to draw the part exactly. */ -void CGUISkin::draw3DButtonPanePressed(IGUIElement *element, - const core::rect &r, - const core::rect *clip) -{ - if (!Driver) - return; - - core::rect rect = r; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); - - rect.LowerRightCorner.X -= 1; - rect.LowerRightCorner.Y -= 1; - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), rect, clip); - - rect.UpperLeftCorner.X += 1; - rect.UpperLeftCorner.Y += 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); - - rect.UpperLeftCorner.X += 1; - rect.UpperLeftCorner.Y += 1; - - if (!UseGradient) { - Driver->draw2DRectangle(getColor(EGDC_3D_FACE), rect, clip); - } else { - const video::SColor c1 = getColor(EGDC_3D_FACE); - const video::SColor c2 = c1.getInterpolated(getColor(EGDC_3D_DARK_SHADOW), 0.4f); - Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); - } -} - -//! draws a sunken 3d pane -/** Used for drawing the background of edit, combo or check boxes. -\param element: Pointer to the element which wishes to draw this. This parameter -is usually not used by ISkin, but can be used for example by more complex -implementations to find out how to draw the part exactly. -\param bgcolor: Background color. -\param flat: Specifies if the sunken pane should be flat or displayed as sunken -deep into the ground. -\param rect: Defining area where to draw. -\param clip: Clip area. */ -void CGUISkin::draw3DSunkenPane(IGUIElement *element, video::SColor bgcolor, - bool flat, bool fillBackGround, - const core::rect &r, - const core::rect *clip) -{ - if (!Driver) - return; - - core::rect rect = r; - - if (fillBackGround) - Driver->draw2DRectangle(bgcolor, rect, clip); - - if (flat) { - // draw flat sunken pane - - rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); // top - - ++rect.UpperLeftCorner.Y; - rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); // left - - rect = r; - ++rect.UpperLeftCorner.Y; - rect.UpperLeftCorner.X = rect.LowerRightCorner.X - 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); // right - - rect = r; - ++rect.UpperLeftCorner.X; - rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1; - --rect.LowerRightCorner.X; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); // bottom - } else { - // draw deep sunken pane - rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); // top - ++rect.UpperLeftCorner.X; - ++rect.UpperLeftCorner.Y; - --rect.LowerRightCorner.X; - ++rect.LowerRightCorner.Y; - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), rect, clip); - - rect.UpperLeftCorner.X = r.UpperLeftCorner.X; - rect.UpperLeftCorner.Y = r.UpperLeftCorner.Y + 1; - rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1; - rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); // left - ++rect.UpperLeftCorner.X; - ++rect.UpperLeftCorner.Y; - ++rect.LowerRightCorner.X; - --rect.LowerRightCorner.Y; - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), rect, clip); - - rect = r; - rect.UpperLeftCorner.X = rect.LowerRightCorner.X - 1; - ++rect.UpperLeftCorner.Y; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); // right - --rect.UpperLeftCorner.X; - ++rect.UpperLeftCorner.Y; - --rect.LowerRightCorner.X; - --rect.LowerRightCorner.Y; - Driver->draw2DRectangle(getColor(EGDC_3D_LIGHT), rect, clip); - - rect = r; - ++rect.UpperLeftCorner.X; - rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1; - --rect.LowerRightCorner.X; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); // bottom - ++rect.UpperLeftCorner.X; - --rect.UpperLeftCorner.Y; - --rect.LowerRightCorner.X; - --rect.LowerRightCorner.Y; - Driver->draw2DRectangle(getColor(EGDC_3D_LIGHT), rect, clip); - } -} - -//! draws a window background -// return where to draw title bar text. -core::rect CGUISkin::draw3DWindowBackground(IGUIElement *element, - bool drawTitleBar, video::SColor titleBarColor, - const core::rect &r, - const core::rect *clip, - core::rect *checkClientArea) -{ - if (!Driver) { - if (checkClientArea) { - *checkClientArea = r; - } - return r; - } - - core::rect rect = r; - - // top border - rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1; - if (!checkClientArea) { - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); - } - - // left border - rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1; - if (!checkClientArea) { - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); - } - - // right border dark outer line - rect.UpperLeftCorner.X = r.LowerRightCorner.X - 1; - rect.LowerRightCorner.X = r.LowerRightCorner.X; - rect.UpperLeftCorner.Y = r.UpperLeftCorner.Y; - rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - if (!checkClientArea) { - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), rect, clip); - } - - // right border bright innner line - rect.UpperLeftCorner.X -= 1; - rect.LowerRightCorner.X -= 1; - rect.UpperLeftCorner.Y += 1; - rect.LowerRightCorner.Y -= 1; - if (!checkClientArea) { - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); - } - - // bottom border dark outer line - rect.UpperLeftCorner.X = r.UpperLeftCorner.X; - rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1; - rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - rect.LowerRightCorner.X = r.LowerRightCorner.X; - if (!checkClientArea) { - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), rect, clip); - } - - // bottom border bright inner line - rect.UpperLeftCorner.X += 1; - rect.LowerRightCorner.X -= 1; - rect.UpperLeftCorner.Y -= 1; - rect.LowerRightCorner.Y -= 1; - if (!checkClientArea) { - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); - } - - // client area for background - rect = r; - rect.UpperLeftCorner.X += 1; - rect.UpperLeftCorner.Y += 1; - rect.LowerRightCorner.X -= 2; - rect.LowerRightCorner.Y -= 2; - if (checkClientArea) { - *checkClientArea = rect; - } - - if (!checkClientArea) { - if (!UseGradient) { - Driver->draw2DRectangle(getColor(EGDC_3D_FACE), rect, clip); - } else if (Type == EGST_BURNING_SKIN) { - const video::SColor c1 = getColor(EGDC_WINDOW).getInterpolated(0xFFFFFFFF, 0.9f); - const video::SColor c2 = getColor(EGDC_WINDOW).getInterpolated(0xFFFFFFFF, 0.8f); - - Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); - } else { - const video::SColor c2 = getColor(EGDC_3D_SHADOW); - const video::SColor c1 = getColor(EGDC_3D_FACE); - Driver->draw2DRectangle(rect, c1, c1, c1, c2, clip); - } - } - - // title bar - rect = r; - rect.UpperLeftCorner.X += 2; - rect.UpperLeftCorner.Y += 2; - rect.LowerRightCorner.X -= 2; - rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + getSize(EGDS_WINDOW_BUTTON_WIDTH) + 2; - - if (drawTitleBar) { - if (checkClientArea) { - (*checkClientArea).UpperLeftCorner.Y = rect.LowerRightCorner.Y; - } else { - // draw title bar - // if (!UseGradient) - // Driver->draw2DRectangle(titleBarColor, rect, clip); - // else - if (Type == EGST_BURNING_SKIN) { - const video::SColor c = titleBarColor.getInterpolated(video::SColor(titleBarColor.getAlpha(), 255, 255, 255), 0.8f); - Driver->draw2DRectangle(rect, titleBarColor, titleBarColor, c, c, clip); - } else { - const video::SColor c = titleBarColor.getInterpolated(video::SColor(titleBarColor.getAlpha(), 0, 0, 0), 0.2f); - Driver->draw2DRectangle(rect, titleBarColor, c, titleBarColor, c, clip); - } - } - } - - return rect; -} - -//! draws a standard 3d menu pane -/** Used for drawing for menus and context menus. -It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and -EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. -\param element: Pointer to the element which wishes to draw this. This parameter -is usually not used by ISkin, but can be used for example by more complex -implementations to find out how to draw the part exactly. -\param rect: Defining area where to draw. -\param clip: Clip area. */ -void CGUISkin::draw3DMenuPane(IGUIElement *element, - const core::rect &r, const core::rect *clip) -{ - if (!Driver) - return; - - core::rect rect = r; - - if (Type == EGST_BURNING_SKIN) { - rect.UpperLeftCorner.Y -= 3; - draw3DButtonPaneStandard(element, rect, clip); - return; - } - - // in this skin, this is exactly what non pressed buttons look like, - // so we could simply call - // draw3DButtonPaneStandard(element, rect, clip); - // here. - // but if the skin is transparent, this doesn't look that nice. So - // We draw it a little bit better, with some more draw2DRectangle calls, - // but there aren't that much menus visible anyway. - - rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); - - rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), rect, clip); - - rect.UpperLeftCorner.X = r.LowerRightCorner.X - 1; - rect.LowerRightCorner.X = r.LowerRightCorner.X; - rect.UpperLeftCorner.Y = r.UpperLeftCorner.Y; - rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), rect, clip); - - rect.UpperLeftCorner.X -= 1; - rect.LowerRightCorner.X -= 1; - rect.UpperLeftCorner.Y += 1; - rect.LowerRightCorner.Y -= 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); - - rect.UpperLeftCorner.X = r.UpperLeftCorner.X; - rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1; - rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - rect.LowerRightCorner.X = r.LowerRightCorner.X; - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), rect, clip); - - rect.UpperLeftCorner.X += 1; - rect.LowerRightCorner.X -= 1; - rect.UpperLeftCorner.Y -= 1; - rect.LowerRightCorner.Y -= 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); - - rect = r; - rect.UpperLeftCorner.X += 1; - rect.UpperLeftCorner.Y += 1; - rect.LowerRightCorner.X -= 2; - rect.LowerRightCorner.Y -= 2; - - if (!UseGradient) - Driver->draw2DRectangle(getColor(EGDC_3D_FACE), rect, clip); - else { - const video::SColor c1 = getColor(EGDC_3D_FACE); - const video::SColor c2 = getColor(EGDC_3D_SHADOW); - Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); - } -} - -//! draws a standard 3d tool bar -/** Used for drawing for toolbars and menus. -\param element: Pointer to the element which wishes to draw this. This parameter -is usually not used by ISkin, but can be used for example by more complex -implementations to find out how to draw the part exactly. -\param rect: Defining area where to draw. -\param clip: Clip area. */ -void CGUISkin::draw3DToolBar(IGUIElement *element, - const core::rect &r, - const core::rect *clip) -{ - if (!Driver) - return; - - core::rect rect = r; - - rect.UpperLeftCorner.X = r.UpperLeftCorner.X; - rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1; - rect.LowerRightCorner.Y = r.LowerRightCorner.Y; - rect.LowerRightCorner.X = r.LowerRightCorner.X; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), rect, clip); - - rect = r; - rect.LowerRightCorner.Y -= 1; - - if (!UseGradient) { - Driver->draw2DRectangle(getColor(EGDC_3D_FACE), rect, clip); - } else if (Type == EGST_BURNING_SKIN) { - const video::SColor c1 = 0xF0000000 | getColor(EGDC_3D_FACE).color; - const video::SColor c2 = 0xF0000000 | getColor(EGDC_3D_SHADOW).color; - - rect.LowerRightCorner.Y += 1; - Driver->draw2DRectangle(rect, c1, c2, c1, c2, clip); - } else { - const video::SColor c1 = getColor(EGDC_3D_FACE); - const video::SColor c2 = getColor(EGDC_3D_SHADOW); - Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip); - } -} - -//! draws a tab button -/** Used for drawing for tab buttons on top of tabs. -\param element: Pointer to the element which wishes to draw this. This parameter -is usually not used by ISkin, but can be used for example by more complex -implementations to find out how to draw the part exactly. -\param active: Specifies if the tab is currently active. -\param rect: Defining area where to draw. -\param clip: Clip area. */ -void CGUISkin::draw3DTabButton(IGUIElement *element, bool active, - const core::rect &frameRect, const core::rect *clip, EGUI_ALIGNMENT alignment) -{ - if (!Driver) - return; - - core::rect tr = frameRect; - - if (alignment == EGUIA_UPPERLEFT) { - tr.LowerRightCorner.X -= 2; - tr.LowerRightCorner.Y = tr.UpperLeftCorner.Y + 1; - tr.UpperLeftCorner.X += 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), tr, clip); - - // draw left highlight - tr = frameRect; - tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1; - tr.UpperLeftCorner.Y += 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), tr, clip); - - // draw grey background - tr = frameRect; - tr.UpperLeftCorner.X += 1; - tr.UpperLeftCorner.Y += 1; - tr.LowerRightCorner.X -= 2; - Driver->draw2DRectangle(getColor(EGDC_3D_FACE), tr, clip); - - // draw right middle gray shadow - tr.LowerRightCorner.X += 1; - tr.UpperLeftCorner.X = tr.LowerRightCorner.X - 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), tr, clip); - - tr.LowerRightCorner.X += 1; - tr.UpperLeftCorner.X += 1; - tr.UpperLeftCorner.Y += 1; - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), tr, clip); - } else { - tr.LowerRightCorner.X -= 2; - tr.UpperLeftCorner.Y = tr.LowerRightCorner.Y - 1; - tr.UpperLeftCorner.X += 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), tr, clip); - - // draw left highlight - tr = frameRect; - tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1; - tr.LowerRightCorner.Y -= 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), tr, clip); - - // draw grey background - tr = frameRect; - tr.UpperLeftCorner.X += 1; - tr.UpperLeftCorner.Y -= 1; - tr.LowerRightCorner.X -= 2; - tr.LowerRightCorner.Y -= 1; - Driver->draw2DRectangle(getColor(EGDC_3D_FACE), tr, clip); - - // draw right middle gray shadow - tr.LowerRightCorner.X += 1; - tr.UpperLeftCorner.X = tr.LowerRightCorner.X - 1; - // tr.LowerRightCorner.Y -= 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), tr, clip); - - tr.LowerRightCorner.X += 1; - tr.UpperLeftCorner.X += 1; - tr.LowerRightCorner.Y -= 1; - Driver->draw2DRectangle(getColor(EGDC_3D_DARK_SHADOW), tr, clip); - } -} - -//! draws a tab control body -/** \param element: Pointer to the element which wishes to draw this. This parameter -is usually not used by ISkin, but can be used for example by more complex -implementations to find out how to draw the part exactly. -\param border: Specifies if the border should be drawn. -\param background: Specifies if the background should be drawn. -\param rect: Defining area where to draw. -\param clip: Clip area. */ -void CGUISkin::draw3DTabBody(IGUIElement *element, bool border, bool background, - const core::rect &rect, const core::rect *clip, s32 tabHeight, EGUI_ALIGNMENT alignment) -{ - if (!Driver) - return; - - core::rect tr = rect; - - if (tabHeight == -1) - tabHeight = getSize(gui::EGDS_BUTTON_HEIGHT); - - // draw border. - if (border) { - if (alignment == EGUIA_UPPERLEFT) { - // draw left hightlight - tr.UpperLeftCorner.Y += tabHeight + 2; - tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), tr, clip); - - // draw right shadow - tr.UpperLeftCorner.X = rect.LowerRightCorner.X - 1; - tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), tr, clip); - - // draw lower shadow - tr = rect; - tr.UpperLeftCorner.Y = tr.LowerRightCorner.Y - 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), tr, clip); - } else { - // draw left hightlight - tr.LowerRightCorner.Y -= tabHeight + 2; - tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), tr, clip); - - // draw right shadow - tr.UpperLeftCorner.X = rect.LowerRightCorner.X - 1; - tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_SHADOW), tr, clip); - - // draw lower shadow - tr = rect; - tr.LowerRightCorner.Y = tr.UpperLeftCorner.Y + 1; - Driver->draw2DRectangle(getColor(EGDC_3D_HIGH_LIGHT), tr, clip); - } - } - - if (background) { - if (alignment == EGUIA_UPPERLEFT) { - tr = rect; - tr.UpperLeftCorner.Y += tabHeight + 2; - tr.LowerRightCorner.X -= 1; - tr.UpperLeftCorner.X += 1; - tr.LowerRightCorner.Y -= 1; - } else { - tr = rect; - tr.UpperLeftCorner.X += 1; - tr.UpperLeftCorner.Y -= 1; - tr.LowerRightCorner.X -= 1; - tr.LowerRightCorner.Y -= tabHeight + 2; - // tr.UpperLeftCorner.X += 1; - } - - if (!UseGradient) - Driver->draw2DRectangle(getColor(EGDC_3D_FACE), tr, clip); - else { - video::SColor c1 = getColor(EGDC_3D_FACE); - video::SColor c2 = getColor(EGDC_3D_SHADOW); - Driver->draw2DRectangle(tr, c1, c1, c2, c2, clip); - } - } -} - -//! draws an icon, usually from the skin's sprite bank -/** \param parent: Pointer to the element which wishes to draw this icon. -This parameter is usually not used by IGUISkin, but can be used for example -by more complex implementations to find out how to draw the part exactly. -\param icon: Specifies the icon to be drawn. -\param position: The position to draw the icon -\param starttime: The time at the start of the animation -\param currenttime: The present time, used to calculate the frame number -\param loop: Whether the animation should loop or not -\param clip: Clip area. */ -void CGUISkin::drawIcon(IGUIElement *element, EGUI_DEFAULT_ICON icon, - const core::position2di position, - u32 starttime, u32 currenttime, - bool loop, const core::rect *clip) -{ - if (!SpriteBank) - return; - - bool gray = element && !element->isEnabled(); - SpriteBank->draw2DSprite(Icons[icon], position, clip, - Colors[gray ? EGDC_GRAY_WINDOW_SYMBOL : EGDC_WINDOW_SYMBOL], starttime, currenttime, loop, true); -} - -EGUI_SKIN_TYPE CGUISkin::getType() const -{ - return Type; -} - -//! draws a 2d rectangle. -void CGUISkin::draw2DRectangle(IGUIElement *element, - const video::SColor &color, const core::rect &pos, - const core::rect *clip) -{ - Driver->draw2DRectangle(color, pos, clip); -} - -} // end namespace gui -} // end namespace irr diff --git a/irr/src/CGUISkin.h b/irr/src/CGUISkin.h deleted file mode 100644 index 68eae1c73..000000000 --- a/irr/src/CGUISkin.h +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (C) 2002-2012 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#pragma once - -#include "IGUISkin.h" -#include "irrString.h" - -namespace irr -{ -namespace video -{ -class IVideoDriver; -} -namespace gui -{ - -class CGUISkin : public IGUISkin -{ -public: - CGUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver *driver); - - //! destructor - virtual ~CGUISkin(); - - //! returns display density scaling factor - virtual float getScale() const override { return Scale; } - - //! sets display density scaling factor - virtual void setScale(float scale) override { Scale = scale; } - - //! returns default color - video::SColor getColor(EGUI_DEFAULT_COLOR color) const override; - - //! sets a default color - void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor) override; - - //! returns size for the given size type - s32 getSize(EGUI_DEFAULT_SIZE size) const override; - - //! sets a default size - void setSize(EGUI_DEFAULT_SIZE which, s32 size) override; - - //! returns the default font - IGUIFont *getFont(EGUI_DEFAULT_FONT which = EGDF_DEFAULT) const override; - - //! sets a default font - void setFont(IGUIFont *font, EGUI_DEFAULT_FONT which = EGDF_DEFAULT) override; - - //! sets the sprite bank used for drawing icons - void setSpriteBank(IGUISpriteBank *bank) override; - - //! gets the sprite bank used for drawing icons - IGUISpriteBank *getSpriteBank() const override; - - //! Returns a default icon - /** Returns the sprite index within the sprite bank */ - u32 getIcon(EGUI_DEFAULT_ICON icon) const override; - - //! Sets a default icon - /** Sets the sprite index used for drawing icons like arrows, - close buttons and ticks in checkboxes - \param icon: Enum specifying which icon to change - \param index: The sprite index used to draw this icon */ - void setIcon(EGUI_DEFAULT_ICON icon, u32 index) override; - - //! Returns a default text. - /** For example for Message box button captions: - "OK", "Cancel", "Yes", "No" and so on. */ - const wchar_t *getDefaultText(EGUI_DEFAULT_TEXT text) const override; - - //! Sets a default text. - /** For example for Message box button captions: - "OK", "Cancel", "Yes", "No" and so on. */ - void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t *newText) override; - - //! draws a standard 3d button pane - /** Used for drawing for example buttons in normal state. - It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and - EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. - \param rect: Defining area where to draw. - \param clip: Clip area. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. */ - virtual void draw3DButtonPaneStandard(IGUIElement *element, - const core::rect &rect, - const core::rect *clip = 0) override; - - //! draws a pressed 3d button pane - /** Used for drawing for example buttons in pressed state. - It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and - EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. - \param rect: Defining area where to draw. - \param clip: Clip area. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. */ - virtual void draw3DButtonPanePressed(IGUIElement *element, - const core::rect &rect, - const core::rect *clip = 0) override; - - //! draws a sunken 3d pane - /** Used for drawing the background of edit, combo or check boxes. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param bgcolor: Background color. - \param flat: Specifies if the sunken pane should be flat or displayed as sunken - deep into the ground. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DSunkenPane(IGUIElement *element, - video::SColor bgcolor, bool flat, - bool fillBackGround, - const core::rect &rect, - const core::rect *clip = 0) override; - - //! draws a window background - /** Used for drawing the background of dialogs and windows. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param titleBarColor: Title color. - \param drawTitleBar: True to enable title drawing. - \param rect: Defining area where to draw. - \param clip: Clip area. - \param checkClientArea: When set to non-null the function will not draw anything, - but will instead return the clientArea which can be used for drawing by the calling window. - That is the area without borders and without titlebar. - \return Returns rect where it would be good to draw title bar text. This will - work even when checkClientArea is set to a non-null value.*/ - virtual core::rect draw3DWindowBackground(IGUIElement *element, - bool drawTitleBar, video::SColor titleBarColor, - const core::rect &rect, - const core::rect *clip, - core::rect *checkClientArea) override; - - //! draws a standard 3d menu pane - /** Used for drawing for menus and context menus. - It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and - EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DMenuPane(IGUIElement *element, - const core::rect &rect, - const core::rect *clip = 0) override; - - //! draws a standard 3d tool bar - /** Used for drawing for toolbars and menus. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DToolBar(IGUIElement *element, - const core::rect &rect, - const core::rect *clip = 0) override; - - //! draws a tab button - /** Used for drawing for tab buttons on top of tabs. - \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param active: Specifies if the tab is currently active. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DTabButton(IGUIElement *element, bool active, - const core::rect &rect, const core::rect *clip = 0, - EGUI_ALIGNMENT alignment = EGUIA_UPPERLEFT) override; - - //! draws a tab control body - /** \param element: Pointer to the element which wishes to draw this. This parameter - is usually not used by ISkin, but can be used for example by more complex - implementations to find out how to draw the part exactly. - \param border: Specifies if the border should be drawn. - \param background: Specifies if the background should be drawn. - \param rect: Defining area where to draw. - \param clip: Clip area. */ - virtual void draw3DTabBody(IGUIElement *element, bool border, bool background, - const core::rect &rect, const core::rect *clip = 0, s32 tabHeight = -1, - EGUI_ALIGNMENT alignment = EGUIA_UPPERLEFT) override; - - //! draws an icon, usually from the skin's sprite bank - /** \param element: Pointer to the element which wishes to draw this icon. - This parameter is usually not used by IGUISkin, but can be used for example - by more complex implementations to find out how to draw the part exactly. - \param icon: Specifies the icon to be drawn. - \param position: The position to draw the icon - \param starttime: The time at the start of the animation - \param currenttime: The present time, used to calculate the frame number - \param loop: Whether the animation should loop or not - \param clip: Clip area. */ - virtual void drawIcon(IGUIElement *element, EGUI_DEFAULT_ICON icon, - const core::position2di position, - u32 starttime = 0, u32 currenttime = 0, - bool loop = false, const core::rect *clip = 0) override; - - //! draws a 2d rectangle. - /** \param element: Pointer to the element which wishes to draw this icon. - This parameter is usually not used by IGUISkin, but can be used for example - by more complex implementations to find out how to draw the part exactly. - \param color: Color of the rectangle to draw. The alpha component specifies how - transparent the rectangle will be. - \param pos: Position of the rectangle. - \param clip: Pointer to rectangle against which the rectangle will be clipped. - If the pointer is null, no clipping will be performed. */ - virtual void draw2DRectangle(IGUIElement *element, const video::SColor &color, - const core::rect &pos, const core::rect *clip = 0) override; - - //! get the type of this skin - EGUI_SKIN_TYPE getType() const override; - -private: - float Scale = 1.0f; - video::SColor Colors[EGDC_COUNT]; - s32 Sizes[EGDS_COUNT]; - u32 Icons[EGDI_COUNT]; - IGUIFont *Fonts[EGDF_COUNT]; - IGUISpriteBank *SpriteBank; - core::stringw Texts[EGDT_COUNT]; - video::IVideoDriver *Driver; - bool UseGradient; - - EGUI_SKIN_TYPE Type; -}; - -} // end namespace gui -} // end namespace irr From 2208fc06321d476f5c7066de9bc538bba6f94dcd Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Fri, 6 Sep 2024 21:05:38 +0200 Subject: [PATCH 092/200] Move Minetest GUISkin -> Irrlicht CGUISkin --- irr/include/IGUISkin.h | 8 +++ src/gui/guiSkin.cpp => irr/src/CGUISkin.cpp | 56 +++++++++---------- src/gui/guiSkin.h => irr/src/CGUISkin.h | 60 ++------------------- src/client/renderingengine.cpp | 27 ---------- src/gui/CMakeLists.txt | 1 - src/gui/guiButton.cpp | 4 +- src/gui/guiButton.h | 1 - 7 files changed, 42 insertions(+), 115 deletions(-) rename src/gui/guiSkin.cpp => irr/src/CGUISkin.cpp (94%) rename src/gui/guiSkin.h => irr/src/CGUISkin.h (89%) diff --git a/irr/include/IGUISkin.h b/irr/include/IGUISkin.h index 36b510606..b323983ae 100644 --- a/irr/include/IGUISkin.h +++ b/irr/include/IGUISkin.h @@ -437,6 +437,10 @@ public: virtual void draw3DButtonPaneStandard(IGUIElement *element, const core::rect &rect, const core::rect *clip = 0) = 0; + virtual void drawColored3DButtonPaneStandard(IGUIElement* element, + const core::rect& rect, + const core::rect* clip=0, + const video::SColor* colors=0) = 0; //! draws a pressed 3d button pane /** Used for drawing for example buttons in pressed state. @@ -450,6 +454,10 @@ public: virtual void draw3DButtonPanePressed(IGUIElement *element, const core::rect &rect, const core::rect *clip = 0) = 0; + virtual void drawColored3DButtonPanePressed(IGUIElement* element, + const core::rect& rect, + const core::rect* clip=0, + const video::SColor* colors=0) = 0; //! draws a sunken 3d pane /** Used for drawing the background of edit, combo or check boxes. diff --git a/src/gui/guiSkin.cpp b/irr/src/CGUISkin.cpp similarity index 94% rename from src/gui/guiSkin.cpp rename to irr/src/CGUISkin.cpp index 0ecc80f02..e9721a5fa 100644 --- a/src/gui/guiSkin.cpp +++ b/irr/src/CGUISkin.cpp @@ -4,7 +4,7 @@ // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h -#include "guiSkin.h" +#include "CGUISkin.h" #include "IGUIFont.h" #include "IGUISpriteBank.h" @@ -17,11 +17,11 @@ namespace irr namespace gui { -GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) +CGUISkin::CGUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) : SpriteBank(0), Driver(driver), Type(type) { #ifdef _DEBUG - setDebugName("GUISkin"); + setDebugName("CGUISkin"); #endif if ((Type == EGST_WINDOWS_CLASSIC) || (Type == EGST_WINDOWS_METALLIC)) @@ -167,7 +167,7 @@ GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) //! destructor -GUISkin::~GUISkin() +CGUISkin::~CGUISkin() { for (u32 i=0; i= EGDF_COUNT) return; @@ -244,14 +244,14 @@ void GUISkin::setFont(IGUIFont* font, EGUI_DEFAULT_FONT which) //! gets the sprite bank stored -IGUISpriteBank* GUISkin::getSpriteBank() const +IGUISpriteBank* CGUISkin::getSpriteBank() const { return SpriteBank; } //! set a new sprite bank or remove one by passing 0 -void GUISkin::setSpriteBank(IGUISpriteBank* bank) +void CGUISkin::setSpriteBank(IGUISpriteBank* bank) { if (bank) bank->grab(); @@ -264,7 +264,7 @@ void GUISkin::setSpriteBank(IGUISpriteBank* bank) //! Returns a default icon -u32 GUISkin::getIcon(EGUI_DEFAULT_ICON icon) const +u32 CGUISkin::getIcon(EGUI_DEFAULT_ICON icon) const { if ((u32)icon < EGDI_COUNT) return Icons[icon]; @@ -274,7 +274,7 @@ u32 GUISkin::getIcon(EGUI_DEFAULT_ICON icon) const //! Sets a default icon -void GUISkin::setIcon(EGUI_DEFAULT_ICON icon, u32 index) +void CGUISkin::setIcon(EGUI_DEFAULT_ICON icon, u32 index) { if ((u32)icon < EGDI_COUNT) Icons[icon] = index; @@ -283,7 +283,7 @@ void GUISkin::setIcon(EGUI_DEFAULT_ICON icon, u32 index) //! Returns a default text. For example for Message box button captions: //! "OK", "Cancel", "Yes", "No" and so on. -const wchar_t* GUISkin::getDefaultText(EGUI_DEFAULT_TEXT text) const +const wchar_t* CGUISkin::getDefaultText(EGUI_DEFAULT_TEXT text) const { if ((u32)text < EGDT_COUNT) return Texts[text].c_str(); @@ -294,7 +294,7 @@ const wchar_t* GUISkin::getDefaultText(EGUI_DEFAULT_TEXT text) const //! Sets a default text. For example for Message box button captions: //! "OK", "Cancel", "Yes", "No" and so on. -void GUISkin::setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText) +void CGUISkin::setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText) { if ((u32)which < EGDT_COUNT) Texts[which] = newText; @@ -311,7 +311,7 @@ EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. is usually not used by ISkin, but can be used for example by more complex implementations to find out how to draw the part exactly. */ // PATCH -void GUISkin::drawColored3DButtonPaneStandard(IGUIElement* element, +void CGUISkin::drawColored3DButtonPaneStandard(IGUIElement* element, const core::rect& r, const core::rect* clip, const video::SColor* colors) @@ -373,7 +373,7 @@ EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details. is usually not used by ISkin, but can be used for example by more complex implementations to find out how to draw the part exactly. */ // PATCH -void GUISkin::drawColored3DButtonPanePressed(IGUIElement* element, +void CGUISkin::drawColored3DButtonPanePressed(IGUIElement* element, const core::rect& r, const core::rect* clip, const video::SColor* colors) @@ -423,7 +423,7 @@ deep into the ground. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolor, +void CGUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolor, bool flat, bool fillBackGround, const core::rect& r, const core::rect* clip, @@ -512,7 +512,7 @@ void GUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolo //! draws a window background // return where to draw title bar text. // PATCH -core::rect GUISkin::drawColored3DWindowBackground(IGUIElement* element, +core::rect CGUISkin::drawColored3DWindowBackground(IGUIElement* element, bool drawTitleBar, video::SColor titleBarColor, const core::rect& r, const core::rect* clip, @@ -667,7 +667,7 @@ implementations to find out how to draw the part exactly. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DMenuPane(IGUIElement* element, +void CGUISkin::drawColored3DMenuPane(IGUIElement* element, const core::rect& r, const core::rect* clip, const video::SColor* colors) { @@ -751,7 +751,7 @@ implementations to find out how to draw the part exactly. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DToolBar(IGUIElement* element, +void CGUISkin::drawColored3DToolBar(IGUIElement* element, const core::rect& r, const core::rect* clip, const video::SColor* colors) @@ -804,7 +804,7 @@ implementations to find out how to draw the part exactly. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DTabButton(IGUIElement* element, bool active, +void CGUISkin::drawColored3DTabButton(IGUIElement* element, bool active, const core::rect& frameRect, const core::rect* clip, EGUI_ALIGNMENT alignment, const video::SColor* colors) { @@ -891,7 +891,7 @@ implementations to find out how to draw the part exactly. \param rect: Defining area where to draw. \param clip: Clip area. */ // PATCH -void GUISkin::drawColored3DTabBody(IGUIElement* element, bool border, bool background, +void CGUISkin::drawColored3DTabBody(IGUIElement* element, bool border, bool background, const core::rect& rect, const core::rect* clip, s32 tabHeight, EGUI_ALIGNMENT alignment, const video::SColor* colors) { @@ -989,7 +989,7 @@ by more complex implementations to find out how to draw the part exactly. \param loop: Whether the animation should loop or not \param clip: Clip area. */ // PATCH -void GUISkin::drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon, +void CGUISkin::drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon, const core::position2di position, u32 starttime, u32 currenttime, bool loop, const core::rect* clip, @@ -1008,14 +1008,14 @@ void GUISkin::drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon, // END PATCH -EGUI_SKIN_TYPE GUISkin::getType() const +EGUI_SKIN_TYPE CGUISkin::getType() const { return Type; } //! draws a 2d rectangle. -void GUISkin::draw2DRectangle(IGUIElement* element, +void CGUISkin::draw2DRectangle(IGUIElement* element, const video::SColor &color, const core::rect& pos, const core::rect* clip) { @@ -1025,7 +1025,7 @@ void GUISkin::draw2DRectangle(IGUIElement* element, //! gets the colors // PATCH -void GUISkin::getColors(video::SColor* colors) +void CGUISkin::getColors(video::SColor* colors) { u32 i; for (i=0; isetColor(EGDC_3D_FACE, button_color); \ - skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \ - skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \ - skin->setColor(EGDC_3D_LIGHT, button_color); \ - skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \ - } - - #define getElementSkinColor(color) \ - { \ - if (!Colors) \ - { \ - IGUISkin* skin = Environment->getSkin(); \ - if (skin) \ - return skin->getColor(color); \ - } \ - return Colors[color]; \ - } - - #define setElementSkinColor(which, newColor, shading) \ - { \ - if (!Colors) \ - { \ - Colors = new video::SColor[EGDC_COUNT]; \ - GUISkin* skin = (GUISkin *)Environment->getSkin(); \ - if (skin) \ - skin->getColors(Colors); \ - } \ - Colors[which] = newColor; \ - setShading(Colors[which],shading); \ - } } // end namespace gui -//! Sets the shading -inline void setShading(video::SColor &color,f32 s) // :PATCH: -{ - if (s < 1.0f) - { - color.setRed(color.getRed() * s); - color.setGreen(color.getGreen() * s); - color.setBlue(color.getBlue() * s); - } - else if (s > 1.0f) - { - s -= 1.0f; - - color.setRed(color.getRed() + (255 - color.getRed()) * s); - color.setGreen(color.getGreen() + (255 - color.getGreen()) * s); - color.setBlue(color.getBlue() + (255 - color.getBlue()) * s); - } -} } // end namespace irr -#endif diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 4400dd90e..c4933e062 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -36,7 +36,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inputhandler.h" #include "gettext.h" #include "filesys.h" -#include "../gui/guiSkin.h" #include "irrlicht_changes/static_text.h" #include "irr_ptr.h" @@ -126,27 +125,6 @@ IShaderConstantSetter *FogShaderConstantSetterFactory::create() /* Other helpers */ -static gui::GUISkin *createSkin(gui::IGUIEnvironment *environment, - gui::EGUI_SKIN_TYPE type, video::IVideoDriver *driver) -{ - gui::GUISkin *skin = new gui::GUISkin(type, driver); - - gui::IGUIFont *builtinfont = environment->getBuiltInFont(); - gui::IGUIFontBitmap *bitfont = nullptr; - if (builtinfont && builtinfont->getType() == gui::EGFT_BITMAP) - bitfont = (gui::IGUIFontBitmap*)builtinfont; - - gui::IGUISpriteBank *bank = 0; - skin->setFont(builtinfont); - - if (bitfont) - bank = bitfont->getSpriteBank(); - - skin->setSpriteBank(bank); - - return skin; -} - static std::optional chooseVideoDriver() { auto &&configured_name = g_settings->get("video_driver"); @@ -250,11 +228,6 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) s_singleton = this; - auto skin = createSkin(m_device->getGUIEnvironment(), - gui::EGST_WINDOWS_METALLIC, driver); - m_device->getGUIEnvironment()->setSkin(skin); - skin->drop(); - g_settings->registerChangedCallback("fullscreen", settingChangedCallback, this); g_settings->registerChangedCallback("window_maximized", settingChangedCallback, this); } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 73bbecb02..04a03609d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -19,7 +19,6 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiScene.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollContainer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index d78433edd..9592ba922 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -253,8 +253,8 @@ void GUIButton::draw() setFromState(); } - GUISkin* skin = dynamic_cast(Environment->getSkin()); video::IVideoDriver* driver = Environment->getVideoDriver(); + IGUISkin *skin = Environment->getSkin(); // END PATCH if (DrawBorder) @@ -737,7 +737,7 @@ void GUIButton::setFromStyle(const StyleSpec& style) Padding.UpperLeftCorner + BgMiddle.UpperLeftCorner, Padding.LowerRightCorner + BgMiddle.LowerRightCorner); - GUISkin* skin = dynamic_cast(Environment->getSkin()); + IGUISkin *skin = Environment->getSkin(); core::vector2d defaultPressOffset( skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X), skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)); diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h index 4fad8747c..dd71788ba 100644 --- a/src/gui/guiButton.h +++ b/src/gui/guiButton.h @@ -10,7 +10,6 @@ #include "IGUISpriteBank.h" #include "ITexture.h" #include "SColor.h" -#include "guiSkin.h" #include "StyleSpec.h" using namespace irr; From 733a019bf5aefddb824e5643b791ca25fc620b03 Mon Sep 17 00:00:00 2001 From: sfence Date: Sun, 8 Sep 2024 13:53:43 +0200 Subject: [PATCH 093/200] macOS: make mute sound actually work (#15128) --- src/client/sound/sound_manager.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/client/sound/sound_manager.cpp b/src/client/sound/sound_manager.cpp index 679d3a155..6aae5bb7f 100644 --- a/src/client/sound/sound_manager.cpp +++ b/src/client/sound/sound_manager.cpp @@ -29,6 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "porting.h" +#include + namespace sound { void OpenALSoundManager::stepStreams(f32 dtime) @@ -347,6 +349,13 @@ void OpenALSoundManager::updateListener(const v3f &pos_, const v3f &vel_, void OpenALSoundManager::setListenerGain(f32 gain) { +#if defined(__APPLE__) + /* macOS OpenAL implementation ignore setting AL_GAIN to zero + * so we use smallest possible value + */ + if (gain == 0.0f) + gain = std::numeric_limits::min(); +#endif alListenerf(AL_GAIN, gain); } From 8617993386991eef6ff9735f7858cb96b959018f Mon Sep 17 00:00:00 2001 From: sfence Date: Thu, 12 Sep 2024 23:40:03 +0200 Subject: [PATCH 094/200] Add SDL2 options to compiling README (#15136) --- README.md | 1 + doc/compiling/README.md | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index 5724359d6..919cb144c 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ Command-line options Compiling --------- +- [Compiling - common information](doc/compiling/README.md) - [Compiling on GNU/Linux](doc/compiling/linux.md) - [Compiling on Windows](doc/compiling/windows.md) - [Compiling on MacOS](doc/compiling/macos.md) diff --git a/doc/compiling/README.md b/doc/compiling/README.md index a1ab1ebbd..c394b2be0 100644 --- a/doc/compiling/README.md +++ b/doc/compiling/README.md @@ -22,6 +22,7 @@ General options and their default values: MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible PRECOMPILE_HEADERS=FALSE - Precompile some headers (experimental; requires CMake 3.16 or later) PRECOMPILED_HEADERS_PATH= - Path to a file listing all headers to precompile (default points to src/precompiled_headers.txt) + USE_SDL2=TRUE - Build with SDL2; Enables IrrlichtMt device SDL2 ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal) ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations @@ -43,6 +44,9 @@ General options and their default values: Library specific options: + SDL2_DLL - Only if building with SDL2 on Windows; path to libSDL2.dll + SDL2_INCLUDE_DIRS - Only if building with SDL2; directory where SDL.h is located + SDL2_LIBRARIES - Only if building with SDL2; path to libSDL2.a/libSDL2.so/libSDL2.lib CURL_DLL - Only if building with cURL on Windows; path to libcurl.dll CURL_INCLUDE_DIR - Only if building with cURL; directory where curl.h is located CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib From af67353f7aee3d7b6a5d7dcc42784f98f0c935b8 Mon Sep 17 00:00:00 2001 From: grorp Date: Thu, 12 Sep 2024 23:41:47 +0200 Subject: [PATCH 095/200] Only apply "touch_punch_gesture" when wielded item has no on_use callback (#15098) --- doc/lua_api.md | 10 +++++++++- src/client/game.cpp | 2 +- src/itemdef.cpp | 7 +++++-- src/itemdef.h | 3 ++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index ea728cfbe..f50ea0cb5 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -9337,9 +9337,17 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and -- If specified as a table, the field to be used is selected according to -- the current `pointed_thing`. -- There are three possible TouchInteractionMode values: - -- * "user" (meaning depends on client-side settings) -- * "long_dig_short_place" (long tap = dig, short tap = place) -- * "short_dig_long_place" (short tap = dig, long tap = place) + -- * "user": + -- * For `pointed_object`: Equivalent to "short_dig_long_place" if the + -- client-side setting "touch_punch_gesture" is "short_tap" (the + -- default value) and the item is able to punch (i.e. has no on_use + -- callback defined). + -- Equivalent to "long_dig_short_place" otherwise. + -- * For `pointed_node` and `pointed_nothing`: + -- Equivalent to "long_dig_short_place". + -- * The behavior of "user" may change in the future. -- The default value is "user". sound = { diff --git a/src/client/game.cpp b/src/client/game.cpp index 5855be96f..36cb5f4bb 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3357,7 +3357,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) infostream << "Pointing at " << pointed.dump() << std::endl; if (g_touchcontrols) { - auto mode = selected_def.touch_interaction.getMode(pointed.type); + auto mode = selected_def.touch_interaction.getMode(selected_def, pointed.type); g_touchcontrols->applyContextControls(mode); // applyContextControls may change dig/place input. // Update again so that TOSERVER_INTERACT packets have the correct controls set. diff --git a/src/itemdef.cpp b/src/itemdef.cpp index ad2ed4847..220c6fbb6 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -45,7 +45,8 @@ TouchInteraction::TouchInteraction() pointed_object = TouchInteractionMode_USER; } -TouchInteractionMode TouchInteraction::getMode(PointedThingType pointed_type) const +TouchInteractionMode TouchInteraction::getMode(const ItemDefinition &selected_def, + PointedThingType pointed_type) const { TouchInteractionMode result; switch (pointed_type) { @@ -63,7 +64,9 @@ TouchInteractionMode TouchInteraction::getMode(PointedThingType pointed_type) co } if (result == TouchInteractionMode_USER) { - if (pointed_type == POINTEDTHING_OBJECT) + if (pointed_type == POINTEDTHING_OBJECT && !selected_def.usable) + // Only apply when we're actually able to punch the object, i.e. when + // the selected item has no on_use callback defined. result = g_settings->get("touch_punch_gesture") == "long_tap" ? LONG_DIG_SHORT_PLACE : SHORT_DIG_LONG_PLACE; else diff --git a/src/itemdef.h b/src/itemdef.h index 4a227ebe1..44fab8d91 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -71,7 +71,8 @@ struct TouchInteraction TouchInteraction(); // Returns the right mode for the pointed thing and resolves any occurrence // of TouchInteractionMode_USER into an actual mode. - TouchInteractionMode getMode(PointedThingType pointed_type) const; + TouchInteractionMode getMode(const ItemDefinition &selected_def, + PointedThingType pointed_type) const; void serialize(std::ostream &os) const; void deSerialize(std::istream &is); }; From 9c2b2c002cbb2f68822098878cdabc895b2458f1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 30 Aug 2024 15:39:43 +0200 Subject: [PATCH 096/200] Count duplicate packets as congestion indicator --- src/network/mtp/impl.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/network/mtp/impl.cpp b/src/network/mtp/impl.cpp index 00535945e..96baffaf4 100644 --- a/src/network/mtp/impl.cpp +++ b/src/network/mtp/impl.cpp @@ -739,16 +739,16 @@ void Channel::UpdateTimers(float dtime) if (packet_loss_counter > 1.0f) { packet_loss_counter -= 1.0f; - unsigned int packet_loss = 11; /* use a neutral value for initialization */ - unsigned int packets_successful = 0; - //unsigned int packet_too_late = 0; + unsigned int packet_loss; + unsigned int packets_successful; + unsigned int packet_too_late; bool reasonable_amount_of_data_transmitted = false; { MutexAutoLock internal(m_internal_mutex); packet_loss = current_packet_loss; - //packet_too_late = current_packet_too_late; + packet_too_late = current_packet_too_late; packets_successful = current_packet_successful; if (current_bytes_transfered > (unsigned int) (m_window_size*512/2)) { @@ -759,6 +759,11 @@ void Channel::UpdateTimers(float dtime) current_packet_successful = 0; } + // Packets too late means either packet duplication along the way + // or we were too fast in resending it (which should be self-regulating). + // Count this a signal of congestion, like packet loss. + packet_loss = std::min(packet_loss + packet_too_late, packets_successful); + /* dynamic window size */ float successful_to_lost_ratio = 0.0f; bool done = false; From 42af7cc1c5257fee8d437bc9c241cb260e8f8c46 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 30 Aug 2024 15:58:02 +0200 Subject: [PATCH 097/200] Nerf protocol window sizes Probably due to a unit misunderstanding a long time ago the window sizes were quite insane (especially the default). In practice this was sometimes hidden by other bugs, games trying their best to be lightweight or didn't matter on high-quality internet connections. --- src/network/mtp/impl.cpp | 1 + src/network/mtp/internal.h | 17 +++++++++++++---- src/network/mtp/threads.cpp | 1 + 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/network/mtp/impl.cpp b/src/network/mtp/impl.cpp index 96baffaf4..483765ea4 100644 --- a/src/network/mtp/impl.cpp +++ b/src/network/mtp/impl.cpp @@ -751,6 +751,7 @@ void Channel::UpdateTimers(float dtime) packet_too_late = current_packet_too_late; packets_successful = current_packet_successful; + // has half the window even been used? if (current_bytes_transfered > (unsigned int) (m_window_size*512/2)) { reasonable_amount_of_data_transmitted = true; } diff --git a/src/network/mtp/internal.h b/src/network/mtp/internal.h index f6ab1f159..4cf6cb57a 100644 --- a/src/network/mtp/internal.h +++ b/src/network/mtp/internal.h @@ -354,15 +354,24 @@ private: static ConnectionCommandPtr create(ConnectionCommandType type); }; -/* maximum window size to use, 0xFFFF is theoretical maximum. don't think about +/* + * Window sizes to use, in packets (not bytes!). + * 0xFFFF is theoretical maximum. don't think about * touching it, the less you're away from it the more likely data corruption * will occur + * + * Note: window sizes directly translate to maximum possible throughput, e.g. + * (2048 * 512 bytes) / 33ms = 15 MiB/s */ + +// Due to backwards compatibility we have different window sizes for what we'll +// accept from peers vs. what we use for sending. #define MAX_RELIABLE_WINDOW_SIZE 0x8000 +#define MAX_RELIABLE_WINDOW_SIZE_SEND 2048 /* starting value for window size */ -#define START_RELIABLE_WINDOW_SIZE 0x400 +#define START_RELIABLE_WINDOW_SIZE 64 /* minimum value for window size */ -#define MIN_RELIABLE_WINDOW_SIZE 0x40 +#define MIN_RELIABLE_WINDOW_SIZE 32 class Channel { @@ -430,7 +439,7 @@ public: void setWindowSize(long size) { - m_window_size = (u16)rangelim(size, MIN_RELIABLE_WINDOW_SIZE, MAX_RELIABLE_WINDOW_SIZE); + m_window_size = (u16)rangelim(size, MIN_RELIABLE_WINDOW_SIZE, MAX_RELIABLE_WINDOW_SIZE_SEND); } private: diff --git a/src/network/mtp/threads.cpp b/src/network/mtp/threads.cpp index d1a1e2a34..778c771f3 100644 --- a/src/network/mtp/threads.cpp +++ b/src/network/mtp/threads.cpp @@ -327,6 +327,7 @@ void ConnectionSendThread::sendAsPacketReliable(BufferedPacketPtr &p, Channel *c channel->outgoing_reliables_sent.insert(p, (channel->readOutgoingSequenceNumber() - MAX_RELIABLE_WINDOW_SIZE) % (MAX_RELIABLE_WINDOW_SIZE + 1)); + // wtf is this calculation?? ^ } catch (AlreadyExistsException &e) { LOG(derr_con << m_connection->getDesc() From f54f2c1601b7fb23e2bd3cd576f18b0f5d2fc3b3 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 31 Aug 2024 17:30:37 +0200 Subject: [PATCH 098/200] Fix RTT set before value is available --- src/network/mtp/impl.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/network/mtp/impl.cpp b/src/network/mtp/impl.cpp index 483765ea4..22c3f9804 100644 --- a/src/network/mtp/impl.cpp +++ b/src/network/mtp/impl.cpp @@ -995,13 +995,15 @@ bool UDPPeer::isTimedOut(float timeout, std::string &reason) void UDPPeer::reportRTT(float rtt) { - if (rtt < 0.0) { + if (rtt < 0) return; - } RTTStatistics(rtt,"rudp",MAX_RELIABLE_WINDOW_SIZE*10); // use this value to decide the resend timeout - float timeout = getStat(AVG_RTT) * RESEND_TIMEOUT_FACTOR; + const float rtt_stat = getStat(AVG_RTT); + if (rtt_stat < 0) + return; + float timeout = rtt_stat * RESEND_TIMEOUT_FACTOR; if (timeout < RESEND_TIMEOUT_MIN) timeout = RESEND_TIMEOUT_MIN; if (timeout > RESEND_TIMEOUT_MAX) From 72c306d92053bb4079488e0eb67e1b88188b1d63 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 31 Aug 2024 17:16:48 +0200 Subject: [PATCH 099/200] Improve some protocol code log messages also get rid of the very noisy socket debug message that are useless in a world where Wireshark exists. --- src/main.cpp | 1 - src/network/mtp/impl.cpp | 8 ++++- src/network/mtp/threads.cpp | 37 ++++++++++----------- src/network/socket.cpp | 64 ------------------------------------- src/network/socket.h | 2 -- 5 files changed, 26 insertions(+), 86 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 9f358bb66..30db81aa9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -531,7 +531,6 @@ static bool setup_log_params(const Settings &cmd_args) if (cmd_args.getFlag("trace")) { dstream << _("Enabling trace level debug output") << std::endl; g_logger.addOutput(&stderr_output, LL_TRACE); - socket_enable_debug_output = true; } return true; diff --git a/src/network/mtp/impl.cpp b/src/network/mtp/impl.cpp index 22c3f9804..1ef5eb853 100644 --- a/src/network/mtp/impl.cpp +++ b/src/network/mtp/impl.cpp @@ -1009,7 +1009,13 @@ void UDPPeer::reportRTT(float rtt) if (timeout > RESEND_TIMEOUT_MAX) timeout = RESEND_TIMEOUT_MAX; + float timeout_old = getResendTimeout(); setResendTimeout(timeout); + + if (std::abs(timeout - timeout_old) >= 0.001f) { + dout_con << m_connection->getDesc() << " set resend timeout " << timeout + << " (rtt=" << rtt_stat << ") for peer id: " << id << std::endl; + } } bool UDPPeer::Ping(float dtime,SharedBuffer& data) @@ -1129,7 +1135,7 @@ bool UDPPeer::processReliableSendCommand( u16 packets_available = toadd.size(); /* we didn't get a single sequence number no need to fill queue */ if (!have_initial_sequence_number) { - LOG(derr_con << m_connection->getDesc() << "Ran out of sequence numbers!" << std::endl); + dout_con << m_connection->getDesc() << " No sequence numbers available!" << std::endl; return false; } diff --git a/src/network/mtp/threads.cpp b/src/network/mtp/threads.cpp index 778c771f3..50b7fa1e3 100644 --- a/src/network/mtp/threads.cpp +++ b/src/network/mtp/threads.cpp @@ -35,7 +35,6 @@ namespace con #define PROFILE(a) #undef DEBUG_CONNECTION_KBPS #else -/* this mutex is used to achieve log message consistency */ #define PROFILE(a) a //#define DEBUG_CONNECTION_KBPS #undef DEBUG_CONNECTION_KBPS @@ -221,7 +220,8 @@ void ConnectionSendThread::runTimeouts(float dtime, u32 peer_packet_quota) } float resend_timeout = udpPeer->getResendTimeout(); - for (Channel &channel : udpPeer->channels) { + for (int ch = 0; ch < CHANNEL_COUNT; ch++) { + auto &channel = udpPeer->channels[ch]; // Remove timed out incomplete unreliable split packets channel.incoming_splits.removeUnreliableTimedOuts(dtime, peer_timeout); @@ -242,8 +242,8 @@ void ConnectionSendThread::runTimeouts(float dtime, u32 peer_packet_quota) if (!timed_outs.empty()) { dout_con << m_connection->getDesc() << "Skipping re-send of " << timed_outs.size() << - " timed-out reliables to peer_id " << udpPeer->id - << " (half-open)." << std::endl; + " timed-out reliables to peer_id=" << udpPeer->id + << " channel=" << ch << " (half-open)." << std::endl; } continue; } @@ -256,7 +256,14 @@ void ConnectionSendThread::runTimeouts(float dtime, u32 peer_packet_quota) for (const auto &k : timed_outs) resendReliable(channel, k.get(), resend_timeout); + auto ws_old = channel.getWindowSize(); channel.UpdateTimers(dtime); + auto ws_new = channel.getWindowSize(); + if (ws_old != ws_new) { + dout_con << m_connection->getDesc() << + "Window size adjusted to " << ws_new << " for peer_id=" + << udpPeer->id << " channel=" << ch << std::endl; + } } /* send ping if necessary */ @@ -309,12 +316,12 @@ void ConnectionSendThread::rawSend(const BufferedPacket *p) assert(p); try { m_connection->m_udpSocket.Send(p->address, p->data, p->size()); - LOG(dout_con << m_connection->getDesc() - << " rawSend: " << p->size() - << " bytes sent" << std::endl); + //LOG(dout_con << m_connection->getDesc() + // << " rawSend: " << p->size() + // << " bytes sent" << std::endl); } catch (SendFailedException &e) { LOG(derr_con << m_connection->getDesc() - << "Connection::rawSend(): SendFailedException: " + << "SendFailedException: " << e.what() << " to " << p->address.serializeString() << std::endl); } } @@ -686,9 +693,9 @@ void ConnectionSendThread::sendPackets(float dtime, u32 peer_packet_quota) PROFILE(ScopeProfiler peerprofiler(g_profiler, peerIdentifier.str(), SPT_AVG)); - LOG(dout_con << m_connection->getDesc() - << " Handle per peer queues: peer_id=" << peerId - << " packet quota: " << peer->m_increment_packets_remaining << std::endl); + //LOG(dout_con << m_connection->getDesc() + // << " Handle per peer queues: peer_id=" << peerId + // << " packet quota: " << peer->m_increment_packets_remaining << std::endl); // first send queued reliable packets for all peers (if possible) for (unsigned int i = 0; i < CHANNEL_COUNT; i++) { @@ -1191,7 +1198,7 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Control(Channel *chan // an overflow is quite unlikely but as it'd result in major // rtt miscalculation we handle it here if (current_time > p->absolute_send_time) { - float rtt = (current_time - p->absolute_send_time) / 1000.0; + float rtt = (current_time - p->absolute_send_time) / 1000.0f; // Let peer calculate stuff according to it // (avg_rtt and resend_timeout) @@ -1336,12 +1343,6 @@ SharedBuffer ConnectionReceiveThread::handlePacketType_Reliable(Channel *cha << ", seqnum: " << seqnum << std::endl;) m_connection->sendAck(peer->id, channelnum, seqnum); - // we already have this packet so this one was on wire at least - // the current timeout - // we don't know how long this packet was on wire don't do silly guessing - // dynamic_cast(&peer)-> - // reportRTT(dynamic_cast(&peer)->getResendTimeout()); - throw ProcessedSilentlyException("Retransmitting ack for old packet"); } } diff --git a/src/network/socket.cpp b/src/network/socket.cpp index 9fbbaa34e..17e71d860 100644 --- a/src/network/socket.cpp +++ b/src/network/socket.cpp @@ -50,9 +50,6 @@ typedef int socklen_t; #define SOCKET_ERR_STR(e) strerror(e) #endif -// Set to true to enable verbose debug output -bool socket_enable_debug_output = false; // yuck - static bool g_sockets_initialized = false; // Initialize sockets @@ -104,12 +101,6 @@ bool UDPSocket::init(bool ipv6, bool noExceptions) m_addr_family = ipv6 ? AF_INET6 : AF_INET; m_handle = socket(m_addr_family, SOCK_DGRAM, IPPROTO_UDP); - if (socket_enable_debug_output) { - tracestream << "UDPSocket(" << (int)m_handle - << ")::UDPSocket(): ipv6 = " << (ipv6 ? "true" : "false") - << std::endl; - } - if (m_handle < 0) { if (noExceptions) { return false; @@ -135,11 +126,6 @@ bool UDPSocket::init(bool ipv6, bool noExceptions) UDPSocket::~UDPSocket() { - if (socket_enable_debug_output) { - tracestream << "UDPSocket( " << (int)m_handle << ")::~UDPSocket()" - << std::endl; - } - if (m_handle >= 0) { #ifdef _WIN32 closesocket(m_handle); @@ -151,12 +137,6 @@ UDPSocket::~UDPSocket() void UDPSocket::Bind(Address addr) { - if (socket_enable_debug_output) { - tracestream << "UDPSocket(" << (int)m_handle - << ")::Bind(): " << addr.serializeString() << ":" - << addr.getPort() << std::endl; - } - if (addr.getFamily() != m_addr_family) { const char *errmsg = "Socket and bind address families do not match"; @@ -202,30 +182,6 @@ void UDPSocket::Send(const Address &destination, const void *data, int size) if (INTERNET_SIMULATOR) dumping_packet = myrand() % INTERNET_SIMULATOR_PACKET_LOSS == 0; - if (socket_enable_debug_output) { - // Print packet destination and size - tracestream << (int)m_handle << " -> "; - destination.print(tracestream); - tracestream << ", size=" << size; - - // Print packet contents - tracestream << ", data="; - for (int i = 0; i < size && i < 20; i++) { - if (i % 2 == 0) - tracestream << " "; - unsigned int a = ((const unsigned char *)data)[i]; - tracestream << std::hex << std::setw(2) << std::setfill('0') << a; - } - - if (size > 20) - tracestream << "..."; - - if (dumping_packet) - tracestream << " (DUMPED BY INTERNET_SIMULATOR)"; - - tracestream << std::endl; - } - if (dumping_packet) { // Lol let's forget it tracestream << "UDPSocket::Send(): INTERNET_SIMULATOR: dumping packet." @@ -302,26 +258,6 @@ int UDPSocket::Receive(Address &sender, void *data, int size) sender = Address(address_ip, address_port); } - if (socket_enable_debug_output) { - // Print packet sender and size - tracestream << (int)m_handle << " <- "; - sender.print(tracestream); - tracestream << ", size=" << received; - - // Print packet contents - tracestream << ", data="; - for (int i = 0; i < received && i < 20; i++) { - if (i % 2 == 0) - tracestream << " "; - unsigned int a = ((const unsigned char *)data)[i]; - tracestream << std::hex << std::setw(2) << std::setfill('0') << a; - } - if (received > 20) - tracestream << "..."; - - tracestream << std::endl; - } - return received; } diff --git a/src/network/socket.h b/src/network/socket.h index c3758a9d8..28b69c7b8 100644 --- a/src/network/socket.h +++ b/src/network/socket.h @@ -25,8 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "networkexceptions.h" -extern bool socket_enable_debug_output; - void sockets_init(); void sockets_cleanup(); From b12e67699a94505d804c9603a990128ef6621e4c Mon Sep 17 00:00:00 2001 From: nauta-turbidus <88062389+nauta-turbidus@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:42:33 +0200 Subject: [PATCH 100/200] Document negative saturation (#15062) --- doc/lua_api.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index f50ea0cb5..2f94fd796 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8540,8 +8540,17 @@ child will follow movement and rotation of that bone. * Passing no arguments resets lighting to its default values. * `light_definition` is a table with the following optional fields: * `saturation` sets the saturation (vividness; default: `1.0`). - * values > 1 increase the saturation - * values in [0,1] decrease the saturation + * It is applied according to the function `result = b*(1-s) + c*s`, where: + * `c` is the original color + * `b` is the greyscale version of the color with the same luma + * `s` is the saturation set here + * The resulting color always has the same luma (perceived brightness) as the original. + * This means that: + * values > 1 oversaturate + * values < 1 down to 0 desaturate, 0 being entirely greyscale + * values < 0 cause an effect similar to inversion, + but keeping original luma and being symmetrical in terms of saturation + (eg. -1 and 1 is the same saturation and luma, but different hues) * `shadows` is a table that controls ambient shadows * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness) * This value has no effect on clients who have the "Dynamic Shadows" shader disabled. From 38b4505ad7298da2753dd9cb12d75120f9aed60f Mon Sep 17 00:00:00 2001 From: 1F616EMO~nya Date: Fri, 13 Sep 2024 05:42:46 +0800 Subject: [PATCH 101/200] Allow requesting reconnect when mods kick player (#14971) --- builtin/game/misc.lua | 6 +++--- doc/lua_api.md | 5 +++-- src/network/clientpackethandler.cpp | 31 +++++++++++------------------ src/script/lua_api/l_server.cpp | 6 ++++-- src/script/lua_api/l_server.h | 2 +- src/server.cpp | 11 +++------- src/server.h | 4 ++-- 7 files changed, 28 insertions(+), 37 deletions(-) diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index a8a6700f9..91ca738a4 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -6,14 +6,14 @@ local S = core.get_translator("__builtin") -- Misc. API functions -- --- @spec core.kick_player(String, String) :: Boolean -function core.kick_player(player_name, reason) +-- @spec core.kick_player(String, String, Boolean) :: Boolean +function core.kick_player(player_name, reason, reconnect) if type(reason) == "string" then reason = "Kicked: " .. reason else reason = "Kicked." end - return core.disconnect_player(player_name, reason) + return core.disconnect_player(player_name, reason, reconnect) end function core.check_player_privs(name, ...) diff --git a/doc/lua_api.md b/doc/lua_api.md index 2f94fd796..34af38abc 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6999,10 +6999,11 @@ Bans * Returns boolean indicating success * `minetest.unban_player_or_ip(ip_or_name)`: remove ban record matching IP address or name -* `minetest.kick_player(name, [reason])`: disconnect a player with an optional +* `minetest.kick_player(name[, reason[, reconnect]])`: disconnect a player with an optional reason. * Returns boolean indicating success (false if player nonexistent) -* `minetest.disconnect_player(name, [reason])`: disconnect a player with an + * If `reconnect` is true, allow the user to reconnect. +* `minetest.disconnect_player(name[, reason[, reconnect]])`: disconnect a player with an optional reason, this will not prefix with 'Kicked: ' like kick_player. If no reason is given, it will default to 'Disconnected.' * Returns boolean indicating success (false if player nonexistent) diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 4eefd1c59..a1bffbb93 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -204,7 +204,6 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) // to be processed even if the serialization format has // not been agreed yet, the same as TOCLIENT_INIT. m_access_denied = true; - m_access_denied_reason = "Unknown"; if (pkt->getCommand() != TOCLIENT_ACCESS_DENIED) { // Legacy code from 0.4.12 and older but is still used @@ -223,29 +222,23 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) u8 denyCode; *pkt >> denyCode; - if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN || - denyCode == SERVER_ACCESSDENIED_CRASH) { + if (pkt->getRemainingBytes() > 0) *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) + + if (m_access_denied_reason.empty()) { + if (denyCode >= SERVER_ACCESSDENIED_MAX) { + m_access_denied_reason = gettext("Unknown disconnect reason."); + } else if (denyCode != SERVER_ACCESSDENIED_CUSTOM_STRING) { m_access_denied_reason = gettext(accessDeniedStrings[denyCode]); + } + } + + if (denyCode == SERVER_ACCESSDENIED_TOO_MANY_USERS) { + m_access_denied_reconnect = true; + } else if (pkt->getRemainingBytes() > 0) { u8 reconnect; *pkt >> reconnect; m_access_denied_reconnect = reconnect & 1; - } else if (denyCode == SERVER_ACCESSDENIED_CUSTOM_STRING) { - *pkt >> m_access_denied_reason; - } else if (denyCode == SERVER_ACCESSDENIED_TOO_MANY_USERS) { - m_access_denied_reason = gettext(accessDeniedStrings[denyCode]); - m_access_denied_reconnect = true; - } else if (denyCode < SERVER_ACCESSDENIED_MAX) { - m_access_denied_reason = gettext(accessDeniedStrings[denyCode]); - } else { - // Allow us to add new error messages to the - // protocol without raising the protocol version, if we want to. - // Until then (which may be never), this is outside - // of the defined protocol. - *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) - m_access_denied_reason = "Unknown"; } } diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 69f5cf9a0..82170f936 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -365,7 +365,7 @@ int ModApiServer::l_ban_player(lua_State *L) return 1; } -// disconnect_player(name, [reason]) -> success +// disconnect_player(name[, reason[, reconnect]]) -> success int ModApiServer::l_disconnect_player(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -388,7 +388,9 @@ int ModApiServer::l_disconnect_player(lua_State *L) return 1; } - server->DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, message); + bool reconnect = readParam(L, 3, false); + + server->DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, message, reconnect); lua_pushboolean(L, true); return 1; } diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 505dce735..a0fae79bd 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -106,7 +106,7 @@ private: // unban_player_or_ip() static int l_unban_player_or_ip(lua_State *L); - // disconnect_player(name, [reason]) -> success + // disconnect_player(name[, reason[, reconnect]]) -> success static int l_disconnect_player(lua_State *L); // remove_player(name) diff --git a/src/server.cpp b/src/server.cpp index c76155015..92f97172b 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1383,17 +1383,12 @@ void Server::SendBreath(session_t peer_id, u16 breath) } void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason, bool reconnect) + std::string_view custom_reason, bool reconnect) { assert(reason < SERVER_ACCESSDENIED_MAX); NetworkPacket pkt(TOCLIENT_ACCESS_DENIED, 1, peer_id); - pkt << (u8)reason; - if (reason == SERVER_ACCESSDENIED_CUSTOM_STRING) - pkt << custom_reason; - else if (reason == SERVER_ACCESSDENIED_SHUTDOWN || - reason == SERVER_ACCESSDENIED_CRASH) - pkt << custom_reason << (u8)reconnect; + pkt << (u8)reason << custom_reason << (u8)reconnect; Send(&pkt); } @@ -2829,7 +2824,7 @@ void Server::DenySudoAccess(session_t peer_id) void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason, bool reconnect) + std::string_view custom_reason, bool reconnect) { SendAccessDenied(peer_id, reason, custom_reason, reconnect); m_clients.event(peer_id, CSE_SetDenied); diff --git a/src/server.h b/src/server.h index 17eb9d769..58805c667 100644 --- a/src/server.h +++ b/src/server.h @@ -364,7 +364,7 @@ public: void DenySudoAccess(session_t peer_id); void DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason = "", bool reconnect = false); + std::string_view custom_reason = "", bool reconnect = false); void kickAllPlayers(AccessDeniedCode reason, const std::string &str_reason, bool reconnect); void acceptAuth(session_t peer_id, bool forSudoMode); @@ -485,7 +485,7 @@ private: void SendHP(session_t peer_id, u16 hp, bool effect); void SendBreath(session_t peer_id, u16 breath); void SendAccessDenied(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason, bool reconnect = false); + std::string_view custom_reason, bool reconnect = false); void SendDeathscreen(session_t peer_id, bool set_camera_point_target, v3f camera_point_target); void SendItemDef(session_t peer_id, IItemDefManager *itemdef, u16 protocol_version); From a6219ab955ab20a946449879931685554840f6bc Mon Sep 17 00:00:00 2001 From: j-r Date: Sat, 14 Sep 2024 12:09:56 +0200 Subject: [PATCH 102/200] Fix alignment in implicit client hotbar definition Used when an older server doesn't send it. --- src/client/hud.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 3ff83bdae..0a5db6858 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -383,7 +383,7 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) elems.push_back(&minimap); } if (client->getProtoVersion() < 46 && player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) { - hotbar = {HUD_ELEM_HOTBAR, v2f(0.5, 1), "", v2f(), "", 0 , 0, 0, v2f(-0.5, -1), + hotbar = {HUD_ELEM_HOTBAR, v2f(0.5, 1), "", v2f(), "", 0 , 0, 0, v2f(0, -1), v2f(0, -4), v3f(), v2s32(), 0, "", 0}; elems.push_back(&hotbar); } From c54f5a21374b1af83c252b698f397d5dea68ac18 Mon Sep 17 00:00:00 2001 From: DS Date: Sat, 14 Sep 2024 12:10:11 +0200 Subject: [PATCH 103/200] Move std::tie out of headers --- src/object_properties.cpp | 22 ++++++++++++++++++++++ src/object_properties.h | 26 +++----------------------- src/player.cpp | 17 +++++++++++++++++ src/player.h | 21 +++------------------ 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/object_properties.cpp b/src/object_properties.cpp index 5fb6a7d41..7a70714a2 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "util/serialize.h" #include +#include static const video::SColor NULL_BGCOLOR{0, 1, 1, 1}; @@ -85,6 +86,27 @@ std::string ObjectProperties::dump() const return os.str(); } +static auto tie(const ObjectProperties &o) +{ + // Make sure to add new members to this list! + return std::tie( + o.textures, o.colors, o.collisionbox, o.selectionbox, o.visual, o.mesh, + o.damage_texture_modifier, o.nametag, o.infotext, o.wield_item, o.visual_size, + o.nametag_color, o.nametag_bgcolor, o.spritediv, o.initial_sprite_basepos, + o.stepheight, o.automatic_rotate, o.automatic_face_movement_dir_offset, + o.automatic_face_movement_max_rotation_per_sec, o.eye_height, o.zoom_fov, + o.hp_max, o.breath_max, o.glow, o.pointable, o.physical, o.collideWithObjects, + o.rotate_selectionbox, o.is_visible, o.makes_footstep_sound, + o.automatic_face_movement_dir, o.backface_culling, o.static_save, o.use_texture_alpha, + o.shaded, o.show_on_minimap + ); +} + +bool ObjectProperties::operator==(const ObjectProperties &other) const +{ + return tie(*this) == tie(other); +} + bool ObjectProperties::validate() { const char *func = "ObjectProperties::validate(): "; diff --git a/src/object_properties.h b/src/object_properties.h index 1f8384c77..88c2a2678 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -20,11 +20,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include -#include #include #include "irrlichttypes_bloated.h" #include -#include #include #include "util/pointabilities.h" @@ -77,28 +75,10 @@ struct ObjectProperties std::string dump() const; -private: - auto tie() const { - // Make sure to add new members to this list! - return std::tie( - textures, colors, collisionbox, selectionbox, visual, mesh, damage_texture_modifier, - nametag, infotext, wield_item, visual_size, nametag_color, nametag_bgcolor, - spritediv, initial_sprite_basepos, stepheight, automatic_rotate, - automatic_face_movement_dir_offset, automatic_face_movement_max_rotation_per_sec, - eye_height, zoom_fov, hp_max, breath_max, glow, pointable, physical, - collideWithObjects, rotate_selectionbox, is_visible, makes_footstep_sound, - automatic_face_movement_dir, backface_culling, static_save, use_texture_alpha, - shaded, show_on_minimap - ); - } - -public: - bool operator==(const ObjectProperties &other) const { - return tie() == other.tie(); - }; + bool operator==(const ObjectProperties &other) const; bool operator!=(const ObjectProperties &other) const { - return tie() != other.tie(); - }; + return !(*this == other); + } /** * Check limits of some important properties that'd cause exceptions later on. diff --git a/src/player.cpp b/src/player.cpp index fd902aa83..fd25626ca 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "log.h" #include "porting.h" // strlcpy +#include bool is_valid_player_name(std::string_view name) { @@ -229,3 +230,19 @@ void PlayerControl::unpackKeysPressed(u32 keypress_bits) place = keypress_bits & (1 << 8); zoom = keypress_bits & (1 << 9); } + +static auto tie(const PlayerPhysicsOverride &o) +{ + // Make sure to add new members to this list! + return std::tie( + o.speed, o.jump, o.gravity, o.sneak, o.sneak_glitch, o.new_move, o.speed_climb, + o.speed_crouch, o.liquid_fluidity, o.liquid_fluidity_smooth, o.liquid_sink, + o.acceleration_default, o.acceleration_air, o.speed_fast, o.acceleration_fast, + o.speed_walk + ); +} + +bool PlayerPhysicsOverride::operator==(const PlayerPhysicsOverride &other) const +{ + return tie(*this) == tie(other); +} diff --git a/src/player.h b/src/player.h index 7d92808cf..53411fea4 100644 --- a/src/player.h +++ b/src/player.h @@ -24,10 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "constants.h" #include "util/basic_macros.h" #include "util/string.h" -#include #include #include -#include #include #define PLAYERNAME_SIZE 20 @@ -133,23 +131,10 @@ struct PlayerPhysicsOverride float acceleration_fast = 1.f; float speed_walk = 1.f; -private: - auto tie() const { - // Make sure to add new members to this list! - return std::tie( - speed, jump, gravity, sneak, sneak_glitch, new_move, speed_climb, speed_crouch, - liquid_fluidity, liquid_fluidity_smooth, liquid_sink, acceleration_default, - acceleration_air, speed_fast, acceleration_fast, speed_walk - ); - } - -public: - bool operator==(const PlayerPhysicsOverride &other) const { - return tie() == other.tie(); - }; + bool operator==(const PlayerPhysicsOverride &other) const; bool operator!=(const PlayerPhysicsOverride &other) const { - return tie() != other.tie(); - }; + return !(*this == other); + } }; class Map; From f9c0354af17c655e3e385c982fef0c1b67e61336 Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Fri, 13 Sep 2024 13:14:30 +0200 Subject: [PATCH 104/200] Add colorspec_to_table to the Lua API --- doc/lua_api.md | 4 ++++ games/devtest/mods/unittests/color.lua | 17 +++++++++++++++++ games/devtest/mods/unittests/init.lua | 1 + src/script/lua_api/l_util.cpp | 17 +++++++++++++++++ src/script/lua_api/l_util.h | 3 +++ 5 files changed, 42 insertions(+) create mode 100644 games/devtest/mods/unittests/color.lua diff --git a/doc/lua_api.md b/doc/lua_api.md index 34af38abc..ec00b5130 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5655,6 +5655,10 @@ Utilities * `minetest.colorspec_to_bytes(colorspec)`: Converts a ColorSpec to a raw string of four bytes in an RGBA layout, returned as a string. * `colorspec`: The ColorSpec to convert +* `minetest.colorspec_to_table(colorspec)`: Converts a ColorSpec into RGBA table + form. If the ColorSpec is invalid, returns `nil`. You can use this to parse + ColorStrings. + * `colorspec`: The ColorSpec to convert * `minetest.encode_png(width, height, data, [compression])`: Encode a PNG image and return it in string form. * `width`: Width of the image diff --git a/games/devtest/mods/unittests/color.lua b/games/devtest/mods/unittests/color.lua new file mode 100644 index 000000000..86154445c --- /dev/null +++ b/games/devtest/mods/unittests/color.lua @@ -0,0 +1,17 @@ +local function assert_colors_equal(c1, c2) + if type(c1) == "table" and type(c2) == "table" then + assert(c1.r == c2.r and c1.g == c2.g and c1.b == c2.b and c1.a == c2.a) + else + assert(c1 == c2) + end +end + +local function test_color_conversion() + assert_colors_equal(core.colorspec_to_table("#fff"), {r = 255, g = 255, b = 255, a = 255}) + assert_colors_equal(core.colorspec_to_table(0xFF00FF00), {r = 0, g = 255, b = 0, a = 255}) + assert_colors_equal(core.colorspec_to_table("#00000000"), {r = 0, g = 0, b = 0, a = 0}) + assert_colors_equal(core.colorspec_to_table("green"), {r = 0, g = 128, b = 0, a = 255}) + assert_colors_equal(core.colorspec_to_table("gren"), nil) +end + +unittests.register("test_color_conversion", test_color_conversion) diff --git a/games/devtest/mods/unittests/init.lua b/games/devtest/mods/unittests/init.lua index eae003a2a..a967a986f 100644 --- a/games/devtest/mods/unittests/init.lua +++ b/games/devtest/mods/unittests/init.lua @@ -187,6 +187,7 @@ dofile(modpath .. "/raycast.lua") dofile(modpath .. "/inventory.lua") dofile(modpath .. "/load_time.lua") dofile(modpath .. "/on_shutdown.lua") +dofile(modpath .. "/color.lua") -------------- diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 79eb38629..0c66521f7 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -626,6 +626,20 @@ int ModApiUtil::l_colorspec_to_bytes(lua_State *L) return 0; } +// colorspec_to_table(colorspec) +int ModApiUtil::l_colorspec_to_table(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + video::SColor color(0); + if (read_color(L, 1, &color)) { + push_ARGB8(L, color); + return 1; + } + + return 0; +} + // encode_png(w, h, data, level) int ModApiUtil::l_encode_png(lua_State *L) { @@ -726,6 +740,7 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(sha256); API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + API_FCT(colorspec_to_table); API_FCT(encode_png); @@ -761,6 +776,7 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(sha256); API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + API_FCT(colorspec_to_table); API_FCT(get_last_run_mod); API_FCT(set_last_run_mod); @@ -805,6 +821,7 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(sha256); API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + API_FCT(colorspec_to_table); API_FCT(encode_png); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index e0daf3e79..442e0749d 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -122,6 +122,9 @@ private: // colorspec_to_bytes(colorspec) static int l_colorspec_to_bytes(lua_State *L); + // colorspec_to_table(colorspec) + static int l_colorspec_to_table(lua_State *L); + // encode_png(w, h, data, level) static int l_encode_png(lua_State *L); From 7bab390413845f78cf80a0b95e066b9c18e24fe7 Mon Sep 17 00:00:00 2001 From: Gregor Parzefall Date: Fri, 13 Sep 2024 13:14:31 +0200 Subject: [PATCH 105/200] Add time_to_day_night_ratio to the Lua API --- doc/lua_api.md | 4 ++++ src/script/lua_api/l_util.cpp | 15 +++++++++++++++ src/script/lua_api/l_util.h | 3 +++ 3 files changed, 22 insertions(+) diff --git a/doc/lua_api.md b/doc/lua_api.md index ec00b5130..ad8eacb06 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5659,6 +5659,9 @@ Utilities form. If the ColorSpec is invalid, returns `nil`. You can use this to parse ColorStrings. * `colorspec`: The ColorSpec to convert +* `minetest.time_to_day_night_ratio(time_of_day)`: Returns a "day-night ratio" value + (as accepted by `ObjectRef:override_day_night_ratio`) that is equivalent to + the given "time of day" value (as returned by `minetest.get_timeofday`). * `minetest.encode_png(width, height, data, [compression])`: Encode a PNG image and return it in string form. * `width`: Width of the image @@ -8519,6 +8522,7 @@ child will follow movement and rotation of that bone. * `0`...`1`: Overrides day-night ratio, controlling sunlight to a specific amount. * Passing no arguments disables override, defaulting to sunlight based on day-night cycle + * See also `minetest.time_to_day_night_ratio`, * `get_day_night_ratio()`: returns the ratio or nil if it isn't overridden * `set_local_animation(idle, walk, dig, walk_while_dig, frame_speed)`: set animation for player model in third person view. diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 0c66521f7..75a11a050 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -45,6 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "my_sha256.h" #include "util/png.h" #include "player.h" +#include "daynightratio.h" #include // only available in zstd 1.3.5+ @@ -640,6 +641,17 @@ int ModApiUtil::l_colorspec_to_table(lua_State *L) return 0; } +// time_to_day_night_ratio(time_of_day) +int ModApiUtil::l_time_to_day_night_ratio(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + float time_of_day = lua_tonumber(L, 1) * 24000; + u32 dnr = time_to_daynight_ratio(time_of_day, true); + lua_pushnumber(L, dnr / 1000.0f); + return 1; +} + // encode_png(w, h, data, level) int ModApiUtil::l_encode_png(lua_State *L) { @@ -741,6 +753,7 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); API_FCT(colorspec_to_table); + API_FCT(time_to_day_night_ratio); API_FCT(encode_png); @@ -777,6 +790,7 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); API_FCT(colorspec_to_table); + API_FCT(time_to_day_night_ratio); API_FCT(get_last_run_mod); API_FCT(set_last_run_mod); @@ -822,6 +836,7 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); API_FCT(colorspec_to_table); + API_FCT(time_to_day_night_ratio); API_FCT(encode_png); diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 442e0749d..89cc684e1 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -125,6 +125,9 @@ private: // colorspec_to_table(colorspec) static int l_colorspec_to_table(lua_State *L); + // time_to_day_night_ratio(time_of_day) + static int l_time_to_day_night_ratio(lua_State *L); + // encode_png(w, h, data, level) static int l_encode_png(lua_State *L); From 6f23de41fb9566b00757c5f6fee6d3904a40a0d9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 11 Sep 2024 16:40:58 +0200 Subject: [PATCH 106/200] Refresh windows toolchain and libs --- util/buildbot/common.sh | 16 +++++++-------- util/buildbot/download_toolchain.sh | 5 +++-- util/buildbot/sha256sums.txt | 30 ++++++++++++++--------------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/util/buildbot/common.sh b/util/buildbot/common.sh index a7fa3b5d3..ff3aef2e9 100644 --- a/util/buildbot/common.sh +++ b/util/buildbot/common.sh @@ -5,17 +5,17 @@ CORE_NAME=minetest ogg_version=1.3.5 openal_version=1.23.1 vorbis_version=1.3.7 -curl_version=8.5.0 +curl_version=8.9.1 gettext_version=0.20.2 -freetype_version=2.13.2 -sqlite3_version=3.44.2 -luajit_version=20240125 +freetype_version=2.13.3 +sqlite3_version=3.46.1 +luajit_version=20240905 leveldb_version=1.23 zlib_version=1.3.1 -zstd_version=1.5.5 +zstd_version=1.5.6 libjpeg_version=3.0.1 -libpng_version=1.6.40 -sdl2_version=2.30.3 +libpng_version=1.6.43 +sdl2_version=2.30.7 download () { local url=$1 @@ -80,7 +80,7 @@ _dlls () { add_cmake_libs () { cmake_args+=( - -DPNG_LIBRARY=$libdir/libpng/lib/libpng.dll.a + -DPNG_LIBRARY=$libdir/libpng/lib/libpng16.dll.a -DPNG_PNG_INCLUDE_DIR=$libdir/libpng/include -DPNG_DLL="$(_dlls $libdir/libpng/bin/*)" diff --git a/util/buildbot/download_toolchain.sh b/util/buildbot/download_toolchain.sh index 3097f803b..d7d7afbe2 100755 --- a/util/buildbot/download_toolchain.sh +++ b/util/buildbot/download_toolchain.sh @@ -10,8 +10,9 @@ fi # * Clang + LLD + libc++ instead of GCC + binutils + stdc++ # * Mingw-w64 with UCRT enabled and winpthreads support # why are we avoiding GCC? -> Thread Local Storage (TLS) is totally broken -name=llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz -wget "https://github.com/mstorsjo/llvm-mingw/releases/download/20231128/$name" -O "$name" +date=20240619 +name=llvm-mingw-${date}-ucrt-ubuntu-20.04-x86_64.tar.xz +wget "https://github.com/mstorsjo/llvm-mingw/releases/download/$date/$name" -O "$name" sha256sum -w -c <(grep -F "$name" "$topdir/sha256sums.txt") tar -xaf "$name" -C "$1" --strip-components=1 rm -f "$name" diff --git a/util/buildbot/sha256sums.txt b/util/buildbot/sha256sums.txt index 0587e6ea1..310ab6a6e 100644 --- a/util/buildbot/sha256sums.txt +++ b/util/buildbot/sha256sums.txt @@ -1,7 +1,7 @@ -753dc38c591e078eae6a0a6b25f69826211256f444f3691a170670d8a12988f9 curl-8.5.0-win32.zip -aa86abc3eb054d74d5fe15996f281cf84230a61b4ab7b3a702ab7dbb71e1203f curl-8.5.0-win64.zip -3e9d7bbca953b96dfd65acc28baaa87e8881aab29809ba03b9c9aefe3d071189 freetype-2.13.2-win32.zip -acf901e93aedbcfa92eb3aab1def252676af845b1747ca5c3e7c5866576168cc freetype-2.13.2-win64.zip +627d4111ee655a68e806251974ba9d0337efac19cb07d499689c44c328a23775 curl-8.9.1-win32.zip +ed906726531388441d7f93fc0a1c9d567d476fbc8cfbae19dc5a0f7288949abe curl-8.9.1-win64.zip +7a94b9e69d4872489228ad7cca6a16117a433f809d9b20fa3e44e1616a33c5d7 freetype-2.13.3-win32.zip +f7d882319790f72ebc8eff00526388432bd26bff3a56c4ef5cce0a829bbbef0d freetype-2.13.3-win64.zip 41b10766de2773f0f0851fde16b363024685e0397f4bb2e5cd2a7be196960a01 gettext-0.20.2-win32.zip 1ceed167ff16fea944f76ab6ea2969160c71a67419259b17c9c523e7a01eb883 gettext-0.20.2-win64.zip 53dfd31285f470fcf0dca88217c5cf9c557729af6d103afae5936e72ddc38d3c libjpeg-3.0.1-win32.zip @@ -10,20 +10,20 @@ f54e9a577e2db47ed28f4a01e74181d2c607627c551d30f48263e01b59e84f67 libleveldb-1.2 2f039848a4e6c05a2347fe5a7fa63c430dd08d1bc88235645a863c859e14f5f8 libleveldb-1.23-win64.zip 0df94afb8efa361cceb132ecf9491720afbc45ba844a7b1c94607295829b53ca libogg-1.3.5-win32.zip 5c4acb4c99429a04b5e69650719b2eb17616bf52837d2372a0f859952eebce48 libogg-1.3.5-win64.zip -6baf4e819bfb3573760524b5dc9a04b5e479090d6d2046b86cf39a3107c0071f libpng-1.6.40-win32.zip -c02e029f01fce44baea7f4aecfd2564bd8a03507c0c6af8b03339ae0452c8b7d libpng-1.6.40-win64.zip +fb61536bfce414fdecb30dfbdc8b26e87969ee30b420f5fb8542f7573a1c1d12 libpng-1.6.43-win32.zip +ccd0b8ecbaa07028067a99dd4314ec7799445f80a28ddc86fa3f6bf25700177b libpng-1.6.43-win64.zip 456ece10a2be4247b27fbe88f88ddd54aae604736a6b76ba9a922b602fe40f40 libvorbis-1.3.7-win32.zip 57f4db02da00556895bb63fafa6e46b5f7dac87c25fde27af4315f56a1aa7a86 libvorbis-1.3.7-win64.zip -0f21ff3be90311092fe32e0e30878ef3ae9d9437b8d9ac25ef279e0d84e9bb8e llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz -da6ad10632cf172992158e9ea0977a87914b5d5de93a972c3430b6a412237556 luajit-20240125-win32.zip -2b1dabe83d478b398cf9226d96de7fa62c973365c4aea70d27ba5782fb49d2d0 luajit-20240125-win64.zip +27d33157cc252c29ad6f777a96a0d94176fea1b534ff09b5071485def143b90e llvm-mingw-20240619-ucrt-ubuntu-20.04-x86_64.tar.xz +5380bbb0bfd4482b5774e4f7c0ff58cc0857477b88a88a178316a464d3459cf1 luajit-20240905-win32.zip +5805c75c61bf948f790e6f845adc94a4946e43ab8a78c5b5b441550f8a665d2c luajit-20240905-win64.zip e2443451fe5c2066eb564c64b8a1762738a88b7fd749c8b5907fed45c785497b openal-soft-1.23.1-win32.zip cb041445a118469caefbad2647470cb8571c8337bce2adc07634011ab5625417 openal-soft-1.23.1-win64.zip -574e0847e622ff09ab23e2b22b77685a2ab6ee43de3e2932f3e8a14a4d7b9338 sdl2-2.30.3-win32.zip -6127afdfc7b6a4ade8caf9a7267748ffea974f729866dd5be96c7a69d2f0fee7 sdl2-2.30.3-win64.zip -326701086a0ed66e09a9f3ec4d971654c13b6bd79cfdd079c947ecdcd6409525 sqlite3-3.44.2-win32.zip -b2d474e3625f8f426b6cc5c0ecac831a1de46f7d1027bf4a9f6267b0b0411d42 sqlite3-3.44.2-win64.zip +af09a54f1f5d75ef6e1bf63662489ca57d44b6b522446638afe35e59b8456a3c sdl2-2.30.7-win32.zip +613abc34a84ed2c3b050314b340ba7e675879e8ed7848e6a28cd9c50262a33b0 sdl2-2.30.7-win64.zip +9685857ae0b418068ad4324e3711121bda97488d19235a0e68a6060162e014d7 sqlite3-3.46.1-win32.zip +7e2990619b1fd1d5ed654d1df77ea809d4332c2e914ea8bba53b2cf5acdf10ff sqlite3-3.46.1-win64.zip 8af10515d57dbfee5d2106cd66cafa2adeb4270d4c6047ccbf7e8b5d2d50681c zlib-1.3.1-win32.zip ad43f5d23052590c65633530743e5d622cc76b33c109072e6fd7b487aff56bca zlib-1.3.1-win64.zip -3564dabbe17ec4ecae1fb9a78fe48d9f7c71e2b1166456f6ee27e52fd9c84357 zstd-1.5.5-win32.zip -e61b1f327ce2d836d1f8ca00c40ac77d3ab5309135851c98229bbdf82b060ae5 zstd-1.5.5-win64.zip +e1bd36f6da039ee8c1694509f379a5023c05d6c90905a2cbb424f0395167570a zstd-1.5.6-win32.zip +f65b75b04b00f6bda859a7c60667f735c664a893bf7796b38393c16cc40a1a82 zstd-1.5.6-win64.zip From 4aec4fbe6f62688bc7dee3c227c0241eee977c7e Mon Sep 17 00:00:00 2001 From: DS Date: Sun, 15 Sep 2024 13:47:45 +0200 Subject: [PATCH 107/200] Add support for Tracy profiler (#15113) --- CMakeLists.txt | 21 +++ doc/compiling/README.md | 2 + doc/developing/misc.md | 53 +++++++- doc/lua_api.md | 10 ++ irr/src/CMakeLists.txt | 4 + src/CMakeLists.txt | 6 + src/client/clientlauncher.cpp | 5 + src/client/clientmap.cpp | 3 + src/client/content_mapblock.cpp | 3 + src/client/game.cpp | 26 +++- src/client/mapblock_mesh.cpp | 3 + src/client/sound/sound_manager.cpp | 3 + src/cmake_config.h.in | 1 + src/gui/guiEngine.cpp | 7 +- src/porting.cpp | 3 + src/script/cpp_api/s_base.cpp | 8 ++ src/script/cpp_api/s_security.cpp | 12 +- src/server.cpp | 13 ++ src/util/tracy_wrapper.h | 200 +++++++++++++++++++++++++++++ 19 files changed, 379 insertions(+), 4 deletions(-) create mode 100644 src/util/tracy_wrapper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 54a830089..a9a0ef000 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,11 @@ if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE) endif() set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization") +set(BUILD_WITH_TRACY FALSE CACHE BOOL + "Fetch and build with the Tracy profiler client") +set(FETCH_TRACY_GIT_TAG "master" CACHE STRING + "Git tag for fetching Tracy client. Match with your server (gui) version") + set(DEFAULT_RUN_IN_PLACE FALSE) if(WIN32) set(DEFAULT_RUN_IN_PLACE TRUE) @@ -370,3 +375,19 @@ if(BUILD_DOCUMENTATION) ) endif() endif() + +# Fetch Tracy +if(BUILD_WITH_TRACY) + include(FetchContent) + + message(STATUS "Fetching Tracy (${FETCH_TRACY_GIT_TAG})...") + FetchContent_Declare( + tracy + GIT_REPOSITORY https://github.com/wolfpld/tracy.git + GIT_TAG ${FETCH_TRACY_GIT_TAG} + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(tracy) + message(STATUS "Fetching Tracy - done") +endif() diff --git a/doc/compiling/README.md b/doc/compiling/README.md index c394b2be0..bfe91950f 100644 --- a/doc/compiling/README.md +++ b/doc/compiling/README.md @@ -40,6 +40,8 @@ General options and their default values: ENABLE_UPDATE_CHECKER=TRUE - Whether to enable update checks by default INSTALL_DEVTEST=FALSE - Whether the Development Test game should be installed alongside Minetest USE_GPROF=FALSE - Enable profiling using GProf + BUILD_WITH_TRACY=FALSE - Fetch and build with the Tracy profiler client + FETCH_TRACY_GIT_TAG=master - Git tag for fetching Tracy client. Match with your server (gui) version VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar) Library specific options: diff --git a/doc/developing/misc.md b/doc/developing/misc.md index 1d3d8c941..2ac843caf 100644 --- a/doc/developing/misc.md +++ b/doc/developing/misc.md @@ -1,6 +1,6 @@ # Miscellaneous -## Profiling Minetest on Linux +## Profiling Minetest on Linux with perf We will be using a tool called "perf", which you can get by installing `perf` or `linux-perf` or `linux-tools-common`. @@ -36,3 +36,54 @@ Give both files to the developer and also provide: * commit the source was built from and/or modified source code (if applicable) Hotspot will resolve symbols correctly when pointing the sysroot option at the collected libs. + + +## Profiling with Tracy + +[Tracy](https://github.com/wolfpld/tracy) is +> A real time, nanosecond resolution, remote telemetry, hybrid frame and sampling +> profiler for games and other applications. + +It allows one to annotate important functions and generate traces, where one can +see when each individual function call happened, and how long it took. + +Tracy can also record when frames, e.g. server step, start and end, and inspect +frames that took longer than usual. Minetest already contains annotations for +its frames. + +See also [Tracy's official documentation](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf). + +### Installing + +Tracy consists of a client (Minetest) and a server (the gui). + +Install the server, e.g. using your package manager. + +### Building + +Build Minetest with `-DDBUILD_WITH_TRACY=1`, this will fetch Tracy for building +the Tracy client. And use `FETCH_TRACY_GIT_TAG` to get a version matching your +Tracy server, e.g. `-DFETCH_TRACY_GIT_TAG=v0.11.0` if it's `0.11.0`. + +To actually use Tracy, you also have to enable it with Tracy's build options: +``` +-DTRACY_ENABLE=1 -DTRACY_ONLY_LOCALHOST=1 +``` + +See Tracy's documentation for more build options. + +### Using in C++ + +Start the Tracy server and Minetest. You should see Minetest in the menu. + +To actually get useful traces, you have to annotate functions with `ZoneScoped` +macros and recompile. Please refer to Tracy's official documentation. + +### Using in Lua + +Tracy also supports Lua. +If built with Tracy, Minetest loads its API in the global `tracy` table. +See Tracy's official documentation for more information. + +Note: The whole Tracy Lua API is accessible to all mods. And we don't check if it +is or becomes insecure. Run untrusted mods at your own risk. diff --git a/doc/lua_api.md b/doc/lua_api.md index ad8eacb06..83f186804 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -11394,6 +11394,16 @@ Functions: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshi See http://bitop.luajit.org/ for advanced information. +Tracy Profiler +-------------- + +Minetest can be built with support for the Tracy profiler, which can also be +useful for profiling mods and is exposed to Lua as the global `tracy`. + +See doc/developing/misc.md for details. + +Note: This is a development feature and not covered by compatibility promises. + Error Handling -------------- diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index f5bc675e4..742d27f72 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -468,6 +468,10 @@ foreach(object_lib target_include_directories(${object_lib} PRIVATE ${link_includes}) # Add objects from object library to main library target_sources(IrrlichtMt PRIVATE $) + + if(BUILD_WITH_TRACY) + target_link_libraries(${object_lib} PRIVATE Tracy::TracyClient) + endif() endforeach() # Alias target provides add_submodule compatibility diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0ea8a40a9..cad22ca6f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -648,6 +648,9 @@ if(BUILD_CLIENT) if(BUILD_UNITTESTS OR BUILD_BENCHMARKS) target_link_libraries(${PROJECT_NAME} Catch2::Catch2) endif() + if(BUILD_WITH_TRACY) + target_link_libraries(${PROJECT_NAME} Tracy::TracyClient) + endif() if(PRECOMPILE_HEADERS) target_precompile_headers(${PROJECT_NAME} PRIVATE ${PRECOMPILED_HEADERS_LIST}) @@ -715,6 +718,9 @@ if(BUILD_SERVER) if(BUILD_UNITTESTS OR BUILD_BENCHMARKS) target_link_libraries(${PROJECT_NAME}server Catch2::Catch2) endif() + if(BUILD_WITH_TRACY) + target_link_libraries(${PROJECT_NAME}server Tracy::TracyClient) + endif() if(PRECOMPILE_HEADERS) target_precompile_headers(${PROJECT_NAME}server PRIVATE ${PRECOMPILED_HEADERS_LIST}) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index e86fb4425..1c9e397ca 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "renderingengine.h" #include "network/networkexceptions.h" +#include "util/tracy_wrapper.h" #include #include #include @@ -544,15 +545,19 @@ void ClientLauncher::main_menu(MainMenuData *menudata) video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); infostream << "Waiting for other menus" << std::endl; + auto framemarker = FrameMarker("ClientLauncher::main_menu()-wait-frame").started(); while (m_rendering_engine->run() && !*kill) { if (!isMenuActive()) break; driver->beginScene(true, true, video::SColor(255, 128, 128, 128)); m_rendering_engine->get_gui_env()->drawAll(); driver->endScene(); + framemarker.end(); // On some computers framerate doesn't seem to be automatically limited sleep_ms(25); + framemarker.start(); } + framemarker.end(); infostream << "Waited for other menus" << std::endl; auto *cur_control = m_rendering_engine->get_raw_device()->getCursorControl(); diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index b4356dc27..12945ed1b 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "camera.h" // CameraModes #include "util/basic_macros.h" +#include "util/tracy_wrapper.h" #include "client/renderingengine.h" #include @@ -714,6 +715,8 @@ void ClientMap::touchMapBlocks() void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) { + ZoneScoped; + bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; std::string prefix; diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 4f4056668..f7852ad1c 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/basic_macros.h" #include "util/numeric.h" #include "util/directiontables.h" +#include "util/tracy_wrapper.h" #include "mapblock_mesh.h" #include "settings.h" #include "nodedef.h" @@ -1750,6 +1751,8 @@ void MapblockMeshGenerator::drawNode() void MapblockMeshGenerator::generate() { + ZoneScoped; + for (cur_node.p.Z = 0; cur_node.p.Z < data->side_length; cur_node.p.Z++) for (cur_node.p.Y = 0; cur_node.p.Y < data->side_length; cur_node.p.Y++) for (cur_node.p.X = 0; cur_node.p.X < data->side_length; cur_node.p.X++) { diff --git a/src/client/game.cpp b/src/client/game.cpp index 36cb5f4bb..6e01f2ffe 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -79,6 +79,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "hud.h" #include "clientdynamicinfo.h" #include +#include "util/tracy_wrapper.h" #if USE_SOUND #include "client/sound/sound_openal.h" @@ -1140,6 +1141,8 @@ bool Game::startup(bool *kill, void Game::run() { + ZoneScoped; + ProfilerGraph graph; RunStats stats = {}; CameraOrientation cam_view_target = {}; @@ -1167,15 +1170,21 @@ void Game::run() const bool initial_window_maximized = !g_settings->getBool("fullscreen") && g_settings->getBool("window_maximized"); + auto framemarker = FrameMarker("Game::run()-frame").started(); + while (m_rendering_engine->run() && !(*kill || g_gamecallback->shutdown_requested || (server && server->isShutdownRequested()))) { + framemarker.end(); + // Calculate dtime = // m_rendering_engine->run() from this iteration // + Sleep time until the wanted FPS are reached draw_times.limit(device, &dtime, g_menumgr.pausesGame()); + framemarker.start(); + const auto current_dynamic_info = ClientDynamicInfo::getCurrent(); if (!current_dynamic_info.equal(client_display_info)) { client_display_info = current_dynamic_info; @@ -1232,6 +1241,8 @@ void Game::run() } } + framemarker.end(); + RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized); } @@ -1671,9 +1682,13 @@ bool Game::connectToServer(const GameStartData &start_data, fps_control.reset(); + auto framemarker = FrameMarker("Game::connectToServer()-frame").started(); + while (m_rendering_engine->run()) { + framemarker.end(); fps_control.limit(device, &dtime); + framemarker.start(); // Update client and server step(dtime); @@ -1719,6 +1734,7 @@ bool Game::connectToServer(const GameStartData &start_data, // Update status showOverlayMessage(N_("Connecting to server..."), dtime, 20); } + framemarker.end(); } catch (con::PeerNotFoundException &e) { warningstream << "This should not happen. Please report a bug." << std::endl; return false; @@ -1736,9 +1752,11 @@ bool Game::getServerContent(bool *aborted) fps_control.reset(); + auto framemarker = FrameMarker("Game::getServerContent()-frame").started(); while (m_rendering_engine->run()) { - + framemarker.end(); fps_control.limit(device, &dtime); + framemarker.start(); // Update client and server step(dtime); @@ -1804,6 +1822,7 @@ bool Game::getServerContent(bool *aborted) texture_src, dtime, progress); } } + framemarker.end(); *aborted = true; infostream << "Connect aborted [device]" << std::endl; @@ -2773,6 +2792,8 @@ void Game::updatePauseState() inline void Game::step(f32 dtime) { + ZoneScoped; + if (server) { float fps_max = (!device->isWindowFocused() || g_menumgr.pausesGame()) ? g_settings->getFloat("fps_max_unfocused") : @@ -4052,6 +4073,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, const CameraOrientation &cam) { + ZoneScoped; TimeTaker tt_update("Game::updateFrame()"); LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -4311,6 +4333,8 @@ void Game::updateShadows() void Game::drawScene(ProfilerGraph *graph, RunStats *stats) { + ZoneScoped; + const video::SColor fog_color = this->sky->getFogColor(); const video::SColor sky_color = this->sky->getSkyColor(); diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 32d559149..9c0caa9d0 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "minimap.h" #include "content_mapblock.h" #include "util/directiontables.h" +#include "util/tracy_wrapper.h" #include "client/meshgen/collector.h" #include "client/renderingengine.h" #include @@ -611,6 +612,8 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs m_last_crack(-1), m_last_daynight_ratio((u32) -1) { + ZoneScoped; + for (auto &m : m_mesh) m = new scene::SMesh(); m_enable_shaders = data->m_use_shaders; diff --git a/src/client/sound/sound_manager.cpp b/src/client/sound/sound_manager.cpp index 6aae5bb7f..fc171d565 100644 --- a/src/client/sound/sound_manager.cpp +++ b/src/client/sound/sound_manager.cpp @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "sound_singleton.h" #include "util/numeric.h" // myrand() +#include "util/tracy_wrapper.h" #include "filesys.h" #include "porting.h" @@ -501,6 +502,8 @@ void *OpenALSoundManager::run() u64 t_step_start = porting::getTimeMs(); while (true) { + auto framemarker = FrameMarker("OpenALSoundManager::run()-frame").started(); + auto get_time_since_last_step = [&] { return (f32)(porting::getTimeMs() - t_step_start); }; diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index a8eb53edd..5dc6e4b74 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -41,3 +41,4 @@ #cmakedefine01 BUILD_UNITTESTS #cmakedefine01 BUILD_BENCHMARKS #cmakedefine01 USE_SDL2 +#cmakedefine01 BUILD_WITH_TRACY diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index fdc13fa14..988d2924b 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include "client/imagefilters.h" +#include "util/tracy_wrapper.h" #if USE_SOUND #include "client/sound/sound_openal.h" @@ -329,9 +330,12 @@ void GUIEngine::run() fps_control.reset(); - while (m_rendering_engine->run() && !m_startgame && !m_kill) { + auto framemarker = FrameMarker("GUIEngine::run()-frame").started(); + while (m_rendering_engine->run() && !m_startgame && !m_kill) { + framemarker.end(); fps_control.limit(device, &dtime); + framemarker.start(); if (device->isWindowVisible()) { // check if we need to update the "upper left corner"-text @@ -371,6 +375,7 @@ void GUIEngine::run() m_menu->getAndroidUIInput(); #endif } + framemarker.end(); m_script->beforeClose(); diff --git a/src/porting.cpp b/src/porting.cpp index da972926a..f6409c56c 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -73,6 +73,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "log.h" #include "util/string.h" +#include "util/tracy_wrapper.h" #include #include #include @@ -960,6 +961,8 @@ void TrackFreedMemory(size_t amount) void TriggerMemoryTrim() { + ZoneScoped; + constexpr auto MO = std::memory_order_relaxed; if (memory_freed.load(MO) >= MEMORY_TRIM_THRESHOLD) { // Synchronize call diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index e9907f304..bdd2514e6 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -32,6 +32,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #endif +#if BUILD_WITH_TRACY + #include "tracy/TracyLua.hpp" +#endif extern "C" { #include "lualib.h" @@ -95,6 +98,11 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): lua_pushstring(m_luastack, LUA_BITLIBNAME); lua_call(m_luastack, 1, 0); +#if BUILD_WITH_TRACY + // Load tracy lua bindings + tracy::LuaRegister(m_luastack); +#endif + // Make the ScriptApiBase* accessible to ModApiBase #if INDIRECT_SCRIPTAPI_RIDX *(void **)(lua_newuserdata(m_luastack, sizeof(void *))) = this; diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 7c8ba8931..9a4b0763b 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -109,7 +109,12 @@ void ScriptApiSecurity::initializeSecurity() "string", "table", "math", - "bit" + "bit", + // Not sure if completely safe. But if someone enables tracy, they'll + // know what they do. +#if BUILD_WITH_TRACY + "tracy", +#endif }; static const char *io_whitelist[] = { "close", @@ -303,6 +308,11 @@ void ScriptApiSecurity::initializeSecurityClient() "table", "math", "bit", + // Not sure if completely safe. But if someone enables tracy, they'll + // know what they do. +#if BUILD_WITH_TRACY + "tracy", +#endif }; static const char *os_whitelist[] = { "clock", diff --git a/src/server.cpp b/src/server.cpp index 92f97172b..6421fc175 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -75,6 +75,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gameparams.h" #include "particles.h" #include "gettext.h" +#include "util/tracy_wrapper.h" class ClientNotFoundException : public BaseException { @@ -101,6 +102,8 @@ private: void *ServerThread::run() { + ZoneScoped; + BEGIN_DEBUG_EXCEPTION_HANDLER /* @@ -110,6 +113,7 @@ void *ServerThread::run() * server-step frequency. Receive() is used for waiting between the steps. */ + auto framemarker = FrameMarker("ServerThread::run()-frame").started(); try { m_server->AsyncRunStep(0.0f, true); } catch (con::ConnectionBindFailed &e) { @@ -119,10 +123,12 @@ void *ServerThread::run() } catch (ModError &e) { m_server->setAsyncFatalError(e.what()); } + framemarker.end(); float dtime = 0.0f; while (!stopRequested()) { + framemarker.start(); ScopeProfiler spm(g_profiler, "Server::RunStep() (max)", SPT_MAX); u64 t0 = porting::getTimeUs(); @@ -149,6 +155,7 @@ void *ServerThread::run() } dtime = 1e-6f * (porting::getTimeUs() - t0); + framemarker.end(); } END_DEBUG_EXCEPTION_HANDLER @@ -607,6 +614,9 @@ void Server::step() void Server::AsyncRunStep(float dtime, bool initial_step) { + ZoneScoped; + auto framemarker = FrameMarker("Server::AsyncRunStep()-frame").started(); + { // Send blocks to clients SendBlocks(dtime); @@ -1055,6 +1065,9 @@ void Server::AsyncRunStep(float dtime, bool initial_step) void Server::Receive(float timeout) { + ZoneScoped; + auto framemarker = FrameMarker("Server::Receive()-frame").started(); + const u64 t0 = porting::getTimeUs(); const float timeout_us = timeout * 1e6f; auto remaining_time_us = [&]() -> float { diff --git a/src/util/tracy_wrapper.h b/src/util/tracy_wrapper.h new file mode 100644 index 000000000..0c61ba837 --- /dev/null +++ b/src/util/tracy_wrapper.h @@ -0,0 +1,200 @@ +/* +Minetest +Copyright (C) 2024 DS + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/* + * Wrapper for , so that we can use Tracy's macros without + * having it as mandatory dependency. + * + * For annotations that you don't intend to upstream, you can also include + * directly (which also works in irr/). + */ + +#pragma once + +#include "config.h" +#include "util/basic_macros.h" + +#if BUILD_WITH_TRACY + +#include // IWYU pragma: export + +#else + +// Copied from Tracy.hpp + +#define TracyNoop + +#define ZoneNamed(x,y) +#define ZoneNamedN(x,y,z) +#define ZoneNamedC(x,y,z) +#define ZoneNamedNC(x,y,z,w) + +#define ZoneTransient(x,y) +#define ZoneTransientN(x,y,z) + +#define ZoneScoped +#define ZoneScopedN(x) +#define ZoneScopedC(x) +#define ZoneScopedNC(x,y) + +#define ZoneText(x,y) +#define ZoneTextV(x,y,z) +#define ZoneTextF(x,...) +#define ZoneTextVF(x,y,...) +#define ZoneName(x,y) +#define ZoneNameV(x,y,z) +#define ZoneNameF(x,...) +#define ZoneNameVF(x,y,...) +#define ZoneColor(x) +#define ZoneColorV(x,y) +#define ZoneValue(x) +#define ZoneValueV(x,y) +#define ZoneIsActive false +#define ZoneIsActiveV(x) false + +#define FrameMark +#define FrameMarkNamed(x) +#define FrameMarkStart(x) +#define FrameMarkEnd(x) + +#define FrameImage(x,y,z,w,a) + +#define TracyLockable( type, varname ) type varname +#define TracyLockableN( type, varname, desc ) type varname +#define TracySharedLockable( type, varname ) type varname +#define TracySharedLockableN( type, varname, desc ) type varname +#define LockableBase( type ) type +#define SharedLockableBase( type ) type +#define LockMark(x) (void)x +#define LockableName(x,y,z) + +#define TracyPlot(x,y) +#define TracyPlotConfig(x,y,z,w,a) + +#define TracyMessage(x,y) +#define TracyMessageL(x) +#define TracyMessageC(x,y,z) +#define TracyMessageLC(x,y) +#define TracyAppInfo(x,y) + +#define TracyAlloc(x,y) +#define TracyFree(x) +#define TracySecureAlloc(x,y) +#define TracySecureFree(x) + +#define TracyAllocN(x,y,z) +#define TracyFreeN(x,y) +#define TracySecureAllocN(x,y,z) +#define TracySecureFreeN(x,y) + +#define ZoneNamedS(x,y,z) +#define ZoneNamedNS(x,y,z,w) +#define ZoneNamedCS(x,y,z,w) +#define ZoneNamedNCS(x,y,z,w,a) + +#define ZoneTransientS(x,y,z) +#define ZoneTransientNS(x,y,z,w) + +#define ZoneScopedS(x) +#define ZoneScopedNS(x,y) +#define ZoneScopedCS(x,y) +#define ZoneScopedNCS(x,y,z) + +#define TracyAllocS(x,y,z) +#define TracyFreeS(x,y) +#define TracySecureAllocS(x,y,z) +#define TracySecureFreeS(x,y) + +#define TracyAllocNS(x,y,z,w) +#define TracyFreeNS(x,y,z) +#define TracySecureAllocNS(x,y,z,w) +#define TracySecureFreeNS(x,y,z) + +#define TracyMessageS(x,y,z) +#define TracyMessageLS(x,y) +#define TracyMessageCS(x,y,z,w) +#define TracyMessageLCS(x,y,z) + +#define TracySourceCallbackRegister(x,y) +#define TracyParameterRegister(x,y) +#define TracyParameterSetup(x,y,z,w) +#define TracyIsConnected false +#define TracyIsStarted false +#define TracySetProgramName(x) + +#define TracyFiberEnter(x) +#define TracyFiberEnterHint(x,y) +#define TracyFiberLeave + +#endif + + +// Helper for making sure frames end in all possible control flow path +class FrameMarker +{ + const char *m_name; + bool m_started = false; + +public: + FrameMarker(const char *name) : m_name(name) {} + + ~FrameMarker() { end(); } + + DISABLE_CLASS_COPY(FrameMarker) + + FrameMarker(FrameMarker &&other) noexcept : + m_name(other.m_name), m_started(other.m_started) + { + other.m_started = false; + } + + FrameMarker &operator=(FrameMarker &&other) noexcept + { + if (&other != this) { + end(); + m_name = other.m_name; + m_started = other.m_started; + other.m_started = false; + } + return *this; + } + + FrameMarker &&started() && + { + if (!m_started) { + FrameMarkStart(m_name); + m_started = true; + } + return std::move(*this); + } + + void start() + { + // no move happens, because we drop the reference + (void)std::move(*this).started(); + } + + void end() + { + if (m_started) { + m_started = false; + FrameMarkEnd(m_name); + } + } +}; From 47f199e6cb7af2705d6baee9a3e889fae5126183 Mon Sep 17 00:00:00 2001 From: grorp Date: Mon, 16 Sep 2024 10:16:27 +0200 Subject: [PATCH 108/200] Avoid cloud jump when switching between mainmenu and loading screen (#15163) ... by using the same Clouds object for both. The mainmenu clouds already used shaders before. I had to choose between both or neither, so now both the mainmenu clouds and the loading screen clouds use shaders if available. --- src/client/clientlauncher.cpp | 4 +++- src/client/clouds.cpp | 9 +++------ src/client/clouds.h | 6 +++--- src/gui/guiEngine.cpp | 29 ++--------------------------- src/gui/guiEngine.h | 16 +--------------- 5 files changed, 12 insertions(+), 52 deletions(-) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 1c9e397ca..e99fbff42 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -140,8 +140,10 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) // Create the menu clouds // This is only global so it can be used by RenderingEngine::draw_load_screen(). assert(!g_menucloudsmgr && !g_menuclouds); + std::unique_ptr ssrc(createShaderSource()); + ssrc->addShaderConstantSetterFactory(new FogShaderConstantSetterFactory()); g_menucloudsmgr = m_rendering_engine->get_scene_manager()->createNewSceneManager(); - g_menuclouds = new Clouds(g_menucloudsmgr, nullptr, -1, rand()); + g_menuclouds = new Clouds(g_menucloudsmgr, ssrc.get(), -1, rand()); g_menuclouds->setHeight(100.0f); g_menuclouds->update(v3f(0, 0, 0), video::SColor(255, 240, 240, 255)); scene::ICameraSceneNode* camera; diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 64e4b775a..71028768e 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -28,11 +28,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include - -// Menu clouds are created later class Clouds; -Clouds *g_menuclouds = NULL; -scene::ISceneManager *g_menucloudsmgr = NULL; +scene::ISceneManager *g_menucloudsmgr = nullptr; +Clouds *g_menuclouds = nullptr; // Constant for now static constexpr const float cloud_size = BS * 64.0f; @@ -49,9 +47,8 @@ Clouds::Clouds(scene::ISceneManager* mgr, IShaderSource *ssrc, scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), m_seed(seed) { + assert(ssrc); m_enable_shaders = g_settings->getBool("enable_shaders"); - // menu clouds use shader-less clouds for simplicity (ssrc == NULL) - m_enable_shaders = m_enable_shaders && ssrc; m_material.Lighting = false; m_material.BackfaceCulling = true; diff --git a/src/client/clouds.h b/src/client/clouds.h index 332aa81e9..acd4b0cfb 100644 --- a/src/client/clouds.h +++ b/src/client/clouds.h @@ -36,11 +36,11 @@ namespace irr::scene } // Menu clouds +// The mainmenu and the loading screen use the same Clouds object so that the +// clouds don't jump when switching between the two. class Clouds; -extern Clouds *g_menuclouds; - -// Scene manager used for menu clouds extern scene::ISceneManager *g_menucloudsmgr; +extern Clouds *g_menuclouds; class Clouds : public scene::ISceneNode { diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 988d2924b..4a3d53f51 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -142,10 +142,6 @@ GUIEngine::GUIEngine(JoystickController *joystick, // create texture source m_texture_source = std::make_unique(rendering_engine->get_video_driver()); - // create shader source - // (currently only used by clouds) - m_shader_source.reset(createShaderSource()); - // create soundmanager #if USE_SOUND if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) { @@ -296,10 +292,6 @@ void GUIEngine::run() IrrlichtDevice *device = m_rendering_engine->get_raw_device(); video::IVideoDriver *driver = device->getVideoDriver(); - // Always create clouds because they may or may not be - // needed based on the game selected - cloudInit(); - unsigned int text_height = g_fontengine->getTextHeight(); // Reset fog color @@ -395,8 +387,6 @@ GUIEngine::~GUIEngine() m_irr_toplefttext->remove(); - m_cloud.clouds.reset(); - // delete textures for (image_definition &texture : m_textures) { if (texture.texture) @@ -404,26 +394,11 @@ GUIEngine::~GUIEngine() } } -/******************************************************************************/ -void GUIEngine::cloudInit() -{ - m_shader_source->addShaderConstantSetterFactory( - new FogShaderConstantSetterFactory()); - - m_cloud.clouds = make_irr(m_smgr, m_shader_source.get(), -1, rand()); - m_cloud.clouds->setHeight(100.0f); - m_cloud.clouds->update(v3f(0, 0, 0), video::SColor(255,240,240,255)); - - m_cloud.camera = m_smgr->addCameraSceneNode(0, - v3f(0,0,0), v3f(0, 60, 100)); - m_cloud.camera->setFarValue(10000); -} - /******************************************************************************/ void GUIEngine::drawClouds(float dtime) { - m_cloud.clouds->step(dtime*3); - m_smgr->drawAll(); + g_menuclouds->step(dtime * 3); + g_menucloudsmgr->drawAll(); } /******************************************************************************/ diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index fa4e1ebd3..2df0a0b60 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -203,8 +203,6 @@ private: MainMenuData *m_data = nullptr; /** texture source */ std::unique_ptr m_texture_source; - /** shader source */ - std::unique_ptr m_shader_source; /** sound manager */ std::unique_ptr m_sound_manager; @@ -279,23 +277,11 @@ private: /** and text that is in it */ EnrichedString m_toplefttext; - /** initialize cloud subsystem */ - void cloudInit(); /** do preprocessing for cloud subsystem */ void drawClouds(float dtime); - /** internam data required for drawing clouds */ - struct clouddata { - /** pointer to cloud class */ - irr_ptr clouds; - /** camera required for drawing clouds */ - scene::ICameraSceneNode *camera = nullptr; - }; - /** is drawing of clouds enabled atm */ - bool m_clouds_enabled = true; - /** data used to draw clouds */ - clouddata m_cloud; + bool m_clouds_enabled = true; static void fullscreenChangedCallback(const std::string &name, void *data); }; From 740dc0162e4d4891ecbadae7375d5e00b973e7f9 Mon Sep 17 00:00:00 2001 From: grorp Date: Mon, 16 Sep 2024 10:16:55 +0200 Subject: [PATCH 109/200] Don't use fixed pipeline lighting for stars (#15164) --- src/client/sky.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/client/sky.cpp b/src/client/sky.cpp index f309d444c..50ef56498 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "client/mesh.h" #include "client/tile.h" #include "noise.h" // easeCurve #include "profiler.h" @@ -77,10 +78,9 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade // Create materials m_materials[0] = baseMaterial(); - // FIXME: shouldn't this check m_enable_shaders? - m_materials[0].MaterialType = ssrc->getShaderInfo(ssrc->getShader("stars_shader", TILE_MATERIAL_ALPHA)).material; - m_materials[0].Lighting = true; - m_materials[0].ColorMaterial = video::ECM_NONE; + m_materials[0].MaterialType = m_enable_shaders ? + ssrc->getShaderInfo(ssrc->getShader("stars_shader", TILE_MATERIAL_ALPHA)).material : + video::EMT_TRANSPARENT_ALPHA_CHANNEL; m_materials[1] = baseMaterial(); m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; @@ -688,7 +688,10 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) color.a *= alpha; if (color.a <= 0.0f) // Stars are only drawn when not fully transparent return; - m_materials[0].EmissiveColor = color.toSColor(); + if (m_enable_shaders) + m_materials[0].EmissiveColor = color.toSColor(); + else + setMeshBufferColor(m_stars.get(), color.toSColor()); auto sky_rotation = core::matrix4().setRotationAxisRadians(2.0f * M_PI * (wicked_time_of_day - 0.25f), v3f(0.0f, 0.0f, 1.0f)); auto world_matrix = driver->getTransform(video::ETS_WORLD); @@ -835,7 +838,6 @@ void Sky::updateStars() vertices.reserve(4 * m_star_params.count); indices.reserve(6 * m_star_params.count); - video::SColor fallback_color = m_star_params.starcolor; // used on GLES 2 “without shaders” PcgRandom rgen(m_seed); float d = (0.006 / 2) * m_star_params.scale; for (u16 i = 0; i < m_star_params.count; i++) { @@ -854,10 +856,10 @@ void Sky::updateStars() a.rotateVect(p1); a.rotateVect(p2); a.rotateVect(p3); - vertices.push_back(video::S3DVertex(p, {}, fallback_color, {})); - vertices.push_back(video::S3DVertex(p1, {}, fallback_color, {})); - vertices.push_back(video::S3DVertex(p2, {}, fallback_color, {})); - vertices.push_back(video::S3DVertex(p3, {}, fallback_color, {})); + vertices.push_back(video::S3DVertex(p, {}, {}, {})); + vertices.push_back(video::S3DVertex(p1, {}, {}, {})); + vertices.push_back(video::S3DVertex(p2, {}, {}, {})); + vertices.push_back(video::S3DVertex(p3, {}, {}, {})); } for (u16 i = 0; i < m_star_params.count; i++) { indices.push_back(i * 4 + 0); @@ -867,7 +869,8 @@ void Sky::updateStars() indices.push_back(i * 4 + 3); indices.push_back(i * 4 + 0); } - m_stars->setHardwareMappingHint(scene::EHM_STATIC); + if (m_enable_shaders) + m_stars->setHardwareMappingHint(scene::EHM_STATIC); } void Sky::setSkyColors(const SkyColor &sky_color) From 65af606729f7e3c162bf0b77a02570697f784c66 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 10 Sep 2024 18:41:57 +0200 Subject: [PATCH 110/200] Fix CAO mesh lighting with shaders disabled the 'Lighting' material flag does not have portable behavior --- src/client/content_cao.cpp | 30 +++++++++++------------------- src/client/mesh.cpp | 7 ------- src/client/mesh.h | 5 ----- 3 files changed, 11 insertions(+), 31 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 72f24dfca..b87d36c4f 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -186,6 +186,12 @@ static bool logOnce(const std::ostringstream &from, std::ostream &log_to) return true; } +static void setEmissiveColor(scene::ISceneNode *node, video::SColor color) +{ + for (u32 i = 0; i < node->getMaterialCount(); ++i) + node->getMaterial(i).EmissiveColor = color; +} + /* TestCAO */ @@ -774,8 +780,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) // set vertex colors to ensure alpha is set setMeshColor(m_animated_meshnode->getMesh(), video::SColor(0xFFFFFFFF)); - setAnimatedMeshColor(m_animated_meshnode, video::SColor(0xFFFFFFFF)); - setSceneNodeMaterials(m_animated_meshnode); m_animated_meshnode->forEachMaterial([this] (auto &mat) { @@ -923,26 +927,15 @@ void GenericCAO::setNodeLight(const video::SColor &light_color) } if (m_enable_shaders) { - if (m_prop.visual == "upright_sprite") { - if (!m_meshnode) - return; - for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) - m_meshnode->getMaterial(i).EmissiveColor = light_color; - } else { - scene::ISceneNode *node = getSceneNode(); - if (!node) - return; - - for (u32 i = 0; i < node->getMaterialCount(); ++i) { - video::SMaterial &material = node->getMaterial(i); - material.EmissiveColor = light_color; - } - } + auto *node = getSceneNode(); + if (!node) + return; + setEmissiveColor(node, light_color); } else { if (m_meshnode) { setMeshColor(m_meshnode->getMesh(), light_color); } else if (m_animated_meshnode) { - setAnimatedMeshColor(m_animated_meshnode, light_color); + setMeshColor(m_animated_meshnode->getMesh(), light_color); } else if (m_spritenode) { m_spritenode->setColor(light_color); } @@ -1404,7 +1397,6 @@ void GenericCAO::updateTextures(std::string mod) material.MaterialType = m_material_type; material.MaterialTypeParam = m_material_type_param; material.TextureLayers[0].Texture = texture; - material.Lighting = true; material.BackfaceCulling = m_prop.backface_culling; // don't filter low-res textures, makes them look blurry diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 6196e04d4..43c4f475b 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -181,13 +181,6 @@ void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color) ((video::S3DVertex *) (vertices + i * stride))->Color = color; } -void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color) -{ - for (u32 i = 0; i < node->getMaterialCount(); ++i) { - node->getMaterial(i).EmissiveColor = color; - } -} - void setMeshColor(scene::IMesh *mesh, const video::SColor &color) { if (mesh == NULL) diff --git a/src/client/mesh.h b/src/client/mesh.h index 35d3886aa..931a5d818 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -59,11 +59,6 @@ void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color); */ void setMeshColor(scene::IMesh *mesh, const video::SColor &color); -/* - Set a constant color for an animated mesh -*/ -void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color); - /*! * Overwrites the color of a mesh buffer. * The color is darkened based on the normal vector of the vertices. From 0fdcba197f03435d2efeca9de61dc584bd594708 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 10 Sep 2024 18:59:16 +0200 Subject: [PATCH 111/200] Fix VBO hint in content_cao --- src/client/content_cao.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index b87d36c4f..467c74c42 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -814,15 +814,21 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) } /* Set VBO hint */ - // - if shaders are disabled we modify the mesh often - // - sprites are also modified often - // - the wieldmesh sets its own hint - // - bone transformations do not need to modify the vertex data + // wieldmesh sets its own hint, no need to handle it if (m_enable_shaders && (m_meshnode || m_animated_meshnode)) { - if (m_meshnode) + // sprite uses vertex animation + if (m_meshnode && m_prop.visual != "upright_sprite") m_meshnode->getMesh()->setHardwareMappingHint(scene::EHM_STATIC); - if (m_animated_meshnode) - m_animated_meshnode->getMesh()->setHardwareMappingHint(scene::EHM_STATIC); + + if (m_animated_meshnode) { + auto *mesh = m_animated_meshnode->getMesh(); + // skinning happens on the CPU + if (m_animated_meshnode->getJointCount() > 0) + mesh->setHardwareMappingHint(scene::EHM_STREAM, scene::EBT_VERTEX); + else + mesh->setHardwareMappingHint(scene::EHM_STATIC, scene::EBT_VERTEX); + mesh->setHardwareMappingHint(scene::EHM_STATIC, scene::EBT_INDEX); + } } /* don't update while punch texture modifier is active */ From cc26b5384cc0eb1ab54ba5b796c9c2160777fe20 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 10 Sep 2024 20:29:02 +0200 Subject: [PATCH 112/200] Mark buffer as dirty in mesh helpers unclear if this fixes any actual bug --- src/client/mesh.cpp | 11 ++++++++--- src/client/mesh.h | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 43c4f475b..1e32fcfd1 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -34,7 +34,7 @@ inline static void applyShadeFactor(video::SColor& color, float factor) color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255)); } -void applyFacesShading(video::SColor &color, const v3f &normal) +void applyFacesShading(video::SColor &color, const v3f normal) { /* Some drawtypes have normals set to (0, 0, 0), this must result in @@ -133,6 +133,7 @@ void scaleMesh(scene::IMesh *mesh, v3f scale) for (u32 i = 0; i < vertex_count; i++) ((video::S3DVertex *)(vertices + i * stride))->Pos *= scale; + buf->setDirty(scene::EBT_VERTEX); buf->recalculateBoundingBox(); // calculate total bounding box @@ -161,6 +162,7 @@ void translateMesh(scene::IMesh *mesh, v3f vec) for (u32 i = 0; i < vertex_count; i++) ((video::S3DVertex *)(vertices + i * stride))->Pos += vec; + buf->setDirty(scene::EBT_VERTEX); buf->recalculateBoundingBox(); // calculate total bounding box @@ -172,16 +174,17 @@ void translateMesh(scene::IMesh *mesh, v3f vec) mesh->setBoundingBox(bbox); } -void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color) +void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor color) { const u32 stride = getVertexPitchFromType(buf->getVertexType()); u32 vertex_count = buf->getVertexCount(); u8 *vertices = (u8 *) buf->getVertices(); for (u32 i = 0; i < vertex_count; i++) ((video::S3DVertex *) (vertices + i * stride))->Color = color; + buf->setDirty(scene::EBT_VERTEX); } -void setMeshColor(scene::IMesh *mesh, const video::SColor &color) +void setMeshColor(scene::IMesh *mesh, const video::SColor color) { if (mesh == NULL) return; @@ -202,6 +205,7 @@ static void applyToMesh(scene::IMesh *mesh, const F &fn) char *vertices = reinterpret_cast(buf->getVertices()); for (u32 i = 0; i < vertex_count; i++) fn(reinterpret_cast(vertices + i * stride)); + buf->setDirty(scene::EBT_VERTEX); } } @@ -218,6 +222,7 @@ void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolo // Apply shading applyFacesShading(*vc, vertex->Normal); } + buf->setDirty(scene::EBT_VERTEX); } void setMeshColorByNormalXYZ(scene::IMesh *mesh, diff --git a/src/client/mesh.h b/src/client/mesh.h index 931a5d818..c04cfb0af 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., * Applies shading to a color based on the surface's * normal vector. */ -void applyFacesShading(video::SColor &color, const v3f &normal); +void applyFacesShading(video::SColor &color, const v3f normal); /* Create a new cube mesh. @@ -52,12 +52,12 @@ void translateMesh(scene::IMesh *mesh, v3f vec); /*! * Sets a constant color for all vertices in the mesh buffer. */ -void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color); +void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor color); /* Set a constant color for all vertices in the mesh */ -void setMeshColor(scene::IMesh *mesh, const video::SColor &color); +void setMeshColor(scene::IMesh *mesh, const video::SColor color); /*! * Overwrites the color of a mesh buffer. From 6f275e2ba0956e5ec49bf2028dfb11898133af84 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 10 Sep 2024 21:02:22 +0200 Subject: [PATCH 113/200] Remove dead code in COpenGL3MaterialBaseCB --- client/shaders/Irrlicht/Solid.vsh | 1 - irr/src/OpenGL/FixedPipelineRenderer.cpp | 47 +++++++++++------------- irr/src/OpenGL/FixedPipelineRenderer.h | 17 +-------- 3 files changed, 22 insertions(+), 43 deletions(-) diff --git a/client/shaders/Irrlicht/Solid.vsh b/client/shaders/Irrlicht/Solid.vsh index 7379e5bb4..fd7467f5c 100644 --- a/client/shaders/Irrlicht/Solid.vsh +++ b/client/shaders/Irrlicht/Solid.vsh @@ -11,7 +11,6 @@ attribute vec2 inTexCoord0; uniform mat4 uWVPMatrix; uniform mat4 uWVMatrix; -uniform mat4 uNMatrix; uniform mat4 uTMatrix0; uniform float uThickness; diff --git a/irr/src/OpenGL/FixedPipelineRenderer.cpp b/irr/src/OpenGL/FixedPipelineRenderer.cpp index 7c4adf719..f87220b39 100644 --- a/irr/src/OpenGL/FixedPipelineRenderer.cpp +++ b/irr/src/OpenGL/FixedPipelineRenderer.cpp @@ -4,6 +4,7 @@ // For conditions of distribution and use, see copyright notice in Irrlicht.h #include "FixedPipelineRenderer.h" +#include "os.h" #include "IVideoDriver.h" @@ -15,23 +16,20 @@ namespace video // Base callback COpenGL3MaterialBaseCB::COpenGL3MaterialBaseCB() : - FirstUpdateBase(true), WVPMatrixID(-1), WVMatrixID(-1), NMatrixID(-1), + FirstUpdateBase(true), WVPMatrixID(-1), WVMatrixID(-1), FogEnableID(-1), FogTypeID(-1), FogColorID(-1), FogStartID(-1), - FogEndID(-1), FogDensityID(-1), ThicknessID(-1), LightEnable(false), MaterialAmbient(SColorf(0.f, 0.f, 0.f)), MaterialDiffuse(SColorf(0.f, 0.f, 0.f)), MaterialEmissive(SColorf(0.f, 0.f, 0.f)), MaterialSpecular(SColorf(0.f, 0.f, 0.f)), - MaterialShininess(0.f), FogEnable(0), FogType(1), FogColor(SColorf(0.f, 0.f, 0.f, 1.f)), FogStart(0.f), FogEnd(0.f), FogDensity(0.f), Thickness(1.f) + FogEndID(-1), FogDensityID(-1), ThicknessID(-1), Thickness(1.f), FogEnable(false) { } void COpenGL3MaterialBaseCB::OnSetMaterial(const SMaterial &material) { - LightEnable = material.Lighting; - MaterialAmbient = SColorf(material.AmbientColor); - MaterialDiffuse = SColorf(material.DiffuseColor); - MaterialEmissive = SColorf(material.EmissiveColor); - MaterialSpecular = SColorf(material.SpecularColor); - MaterialShininess = material.Shininess; +#ifdef _DEBUG + if (material.Lighting) + os::Printer::log("Lighted material not supported in unified driver.", ELL_INFORMATION); +#endif - FogEnable = material.FogEnable ? 1 : 0; + FogEnable = material.FogEnable; Thickness = (material.Thickness > 0.f) ? material.Thickness : 1.f; } @@ -43,7 +41,6 @@ void COpenGL3MaterialBaseCB::OnSetConstants(IMaterialRendererServices *services, if (FirstUpdateBase) { WVPMatrixID = services->getVertexShaderConstantID("uWVPMatrix"); WVMatrixID = services->getVertexShaderConstantID("uWVMatrix"); - NMatrixID = services->getVertexShaderConstantID("uNMatrix"); FogEnableID = services->getVertexShaderConstantID("uFogEnable"); FogTypeID = services->getVertexShaderConstantID("uFogType"); @@ -56,31 +53,29 @@ void COpenGL3MaterialBaseCB::OnSetConstants(IMaterialRendererServices *services, FirstUpdateBase = false; } - const core::matrix4 W = driver->getTransform(ETS_WORLD); - const core::matrix4 V = driver->getTransform(ETS_VIEW); - const core::matrix4 P = driver->getTransform(ETS_PROJECTION); + const core::matrix4 &W = driver->getTransform(ETS_WORLD); + const core::matrix4 &V = driver->getTransform(ETS_VIEW); + const core::matrix4 &P = driver->getTransform(ETS_PROJECTION); - core::matrix4 Matrix = P * V * W; - services->setPixelShaderConstant(WVPMatrixID, Matrix.pointer(), 16); - - Matrix = V * W; + core::matrix4 Matrix = V * W; services->setPixelShaderConstant(WVMatrixID, Matrix.pointer(), 16); - Matrix.makeInverse(); - services->setPixelShaderConstant(NMatrixID, Matrix.getTransposed().pointer(), 16); + Matrix = P * Matrix; + services->setPixelShaderConstant(WVPMatrixID, Matrix.pointer(), 16); - services->setPixelShaderConstant(FogEnableID, &FogEnable, 1); + s32 TempEnable = FogEnable ? 1 : 0; + services->setPixelShaderConstant(FogEnableID, &TempEnable, 1); if (FogEnable) { SColor TempColor(0); E_FOG_TYPE TempType = EFT_FOG_LINEAR; - bool TempPerFragment = false; - bool TempRange = false; + f32 FogStart, FogEnd, FogDensity; + bool unused = false; - driver->getFog(TempColor, TempType, FogStart, FogEnd, FogDensity, TempPerFragment, TempRange); + driver->getFog(TempColor, TempType, FogStart, FogEnd, FogDensity, unused, unused); - FogType = (s32)TempType; - FogColor = SColorf(TempColor); + s32 FogType = (s32)TempType; + SColorf FogColor(TempColor); services->setPixelShaderConstant(FogTypeID, &FogType, 1); services->setPixelShaderConstant(FogColorID, reinterpret_cast(&FogColor), 4); diff --git a/irr/src/OpenGL/FixedPipelineRenderer.h b/irr/src/OpenGL/FixedPipelineRenderer.h index a07af1df9..89e251a35 100644 --- a/irr/src/OpenGL/FixedPipelineRenderer.h +++ b/irr/src/OpenGL/FixedPipelineRenderer.h @@ -26,7 +26,6 @@ protected: s32 WVPMatrixID; s32 WVMatrixID; - s32 NMatrixID; s32 FogEnableID; s32 FogTypeID; @@ -37,22 +36,8 @@ protected: s32 ThicknessID; - bool LightEnable; - SColorf GlobalAmbient; - SColorf MaterialAmbient; - SColorf MaterialDiffuse; - SColorf MaterialEmissive; - SColorf MaterialSpecular; - f32 MaterialShininess; - - s32 FogEnable; - s32 FogType; - SColorf FogColor; - f32 FogStart; - f32 FogEnd; - f32 FogDensity; - f32 Thickness; + bool FogEnable; }; class COpenGL3MaterialSolidCB : public COpenGL3MaterialBaseCB From 58ea11c2b3101667e7a5ed118bf1cb2204f63586 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 12 Sep 2024 17:18:13 +0200 Subject: [PATCH 114/200] Add some debug helpers around this area --- irr/include/CIndexBuffer.h | 14 ++++++++++++++ irr/include/CVertexBuffer.h | 14 ++++++++++++++ irr/include/IReferenceCounted.h | 8 ++++++-- irr/src/CMakeLists.txt | 3 +++ irr/src/os.h | 1 - 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/irr/include/CIndexBuffer.h b/irr/include/CIndexBuffer.h index 4318ddaab..904b0ab9a 100644 --- a/irr/include/CIndexBuffer.h +++ b/irr/include/CIndexBuffer.h @@ -7,6 +7,13 @@ #include #include "IIndexBuffer.h" +// Define to receive warnings when violating the hw mapping hints +//#define INDEXBUFFER_HINT_DEBUG + +#ifdef INDEXBUFFER_HINT_DEBUG +#include "../src/os.h" +#endif + namespace irr { namespace scene @@ -58,6 +65,13 @@ public: void setDirty() override { ++ChangedID; +#ifdef INDEXBUFFER_HINT_DEBUG + if (MappingHint == EHM_STATIC && HWBuffer) { + char buf[100]; + snprintf_irr(buf, sizeof(buf), "CIndexBuffer @ %p modified, but it has a static hint", this); + os::Printer::log(buf, ELL_WARNING); + } +#endif } u32 getChangedID() const override { return ChangedID; } diff --git a/irr/include/CVertexBuffer.h b/irr/include/CVertexBuffer.h index 3559bbddb..4b3f33688 100644 --- a/irr/include/CVertexBuffer.h +++ b/irr/include/CVertexBuffer.h @@ -7,6 +7,13 @@ #include #include "IVertexBuffer.h" +// Define to receive warnings when violating the hw mapping hints +//#define VERTEXBUFFER_HINT_DEBUG + +#ifdef VERTEXBUFFER_HINT_DEBUG +#include "../src/os.h" +#endif + namespace irr { namespace scene @@ -87,6 +94,13 @@ public: void setDirty() override { ++ChangedID; +#ifdef VERTEXBUFFER_HINT_DEBUG + if (MappingHint == EHM_STATIC && HWBuffer) { + char buf[100]; + snprintf_irr(buf, sizeof(buf), "CVertexBuffer @ %p modified, but it has a static hint", this); + os::Printer::log(buf, ELL_WARNING); + } +#endif } u32 getChangedID() const override { return ChangedID; } diff --git a/irr/include/IReferenceCounted.h b/irr/include/IReferenceCounted.h index e1000d389..68aa20fb6 100644 --- a/irr/include/IReferenceCounted.h +++ b/irr/include/IReferenceCounted.h @@ -42,7 +42,7 @@ class IReferenceCounted public: //! Constructor. IReferenceCounted() : - DebugName(0), ReferenceCounter(1) + ReferenceCounter(1) { } @@ -136,6 +136,7 @@ public: return ReferenceCounter; } +#ifdef _DEBUG //! Returns the debug name of the object. /** The Debugname may only be set and changed by the object itself. This method should only be used in Debug mode. @@ -157,7 +158,10 @@ protected: private: //! The debug name. - const c8 *DebugName; + const c8 *DebugName = nullptr; +#endif + +private: //! The reference counter. Mutable to do reference counting on const objects. mutable s32 ReferenceCounter; diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 742d27f72..22a0d0093 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -510,6 +510,9 @@ target_link_libraries(IrrlichtMt PRIVATE if(WIN32) target_compile_definitions(IrrlichtMt INTERFACE _IRR_WINDOWS_API_) # used in _IRR_DEBUG_BREAK_IF definition in a public header endif() +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_definitions(IrrlichtMt INTERFACE _DEBUG) # same +endif() if(APPLE OR ANDROID OR EMSCRIPTEN) target_compile_definitions(IrrlichtMt PUBLIC IRR_MOBILE_PATHS) endif() diff --git a/irr/src/os.h b/irr/src/os.h index 58699ab3e..bcc096f95 100644 --- a/irr/src/os.h +++ b/irr/src/os.h @@ -52,7 +52,6 @@ public: // prints out a string to the console out stdout or debug log or whatever static void print(const c8 *message, ELOG_LEVEL ll = ELL_INFORMATION); static void log(const c8 *message, ELOG_LEVEL ll = ELL_INFORMATION); - static void log(const wchar_t *message, ELOG_LEVEL ll = ELL_INFORMATION); // The string ": " is added between message and hint static void log(const c8 *message, const c8 *hint, ELOG_LEVEL ll = ELL_INFORMATION); From 6dfd61cba0f31da0895395aa9183ea7b0e2b88e3 Mon Sep 17 00:00:00 2001 From: wrrrzr <161970349+wrrrzr@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:17:55 +0300 Subject: [PATCH 115/200] Fix TODO in joystick code (#15179) --- src/client/joystick_controller.h | 5 +++++ src/gui/guiFormSpecMenu.cpp | 5 ++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h index 98486df43..d74cc3db6 100644 --- a/src/client/joystick_controller.h +++ b/src/client/joystick_controller.h @@ -155,6 +155,11 @@ public: float getMovementDirection(); float getMovementSpeed(); + u8 getJoystickId() const + { + return m_joystick_id; + } + f32 doubling_dtime; private: diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 0371c26f3..40a445a0c 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3976,10 +3976,9 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) } if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { - /* TODO add a check like: - if (event.JoystickEvent != joystick_we_listen_for) + if (event.JoystickEvent.Joystick != m_joystick->getJoystickId()) return false; - */ + bool handled = m_joystick->handleEvent(event.JoystickEvent); if (handled) { if (m_joystick->wasKeyDown(KeyType::ESC)) { From 70e169f1652ecd68d5f32d627d253ec531225183 Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 18 Sep 2024 12:18:28 +0200 Subject: [PATCH 116/200] Drop fixed pipeline lighting stuff (#15165) --- .../shaders/cloud_shader/opengl_vertex.glsl | 4 +- .../shaders/object_shader/opengl_vertex.glsl | 4 +- .../shaders/stars_shader/opengl_fragment.glsl | 4 +- irr/include/EMaterialProps.h | 12 -- irr/include/SMaterial.h | 99 +------------- irr/include/SOverrideMaterial.h | 12 -- irr/src/CAnimatedMeshSceneNode.cpp | 3 - irr/src/CB3DMeshFileLoader.cpp | 20 +-- irr/src/CBillboardSceneNode.cpp | 1 - irr/src/CMeshSceneNode.cpp | 1 - irr/src/CNullDriver.cpp | 12 +- irr/src/COBJMeshFileLoader.cpp | 2 +- irr/src/COBJMeshFileLoader.h | 4 - irr/src/COpenGLDriver.cpp | 121 +----------------- irr/src/CXMeshFileLoader.cpp | 6 +- irr/src/OpenGL/Driver.cpp | 2 - irr/src/OpenGL/FixedPipelineRenderer.cpp | 6 - src/client/camera.cpp | 2 +- src/client/clientmap.cpp | 1 - src/client/clouds.cpp | 5 +- src/client/content_cao.cpp | 69 +--------- src/client/content_cso.cpp | 1 - src/client/hud.cpp | 4 - src/client/mapblock_mesh.cpp | 1 - src/client/mesh.cpp | 2 - src/client/minimap.cpp | 1 - src/client/particles.cpp | 1 - src/client/shader.cpp | 10 +- src/client/shadows/shadowsScreenQuad.cpp | 1 - src/client/sky.cpp | 40 +++--- src/client/wieldmesh.cpp | 24 +--- src/client/wieldmesh.h | 5 +- src/gui/guiScene.cpp | 1 - src/irrlicht_changes/CGUITTFont.cpp | 4 - 34 files changed, 59 insertions(+), 426 deletions(-) diff --git a/client/shaders/cloud_shader/opengl_vertex.glsl b/client/shaders/cloud_shader/opengl_vertex.glsl index 3f2e7d9b3..ebf4aae49 100644 --- a/client/shaders/cloud_shader/opengl_vertex.glsl +++ b/client/shaders/cloud_shader/opengl_vertex.glsl @@ -1,4 +1,4 @@ -uniform lowp vec4 emissiveColor; +uniform lowp vec4 materialColor; varying lowp vec4 varColor; @@ -14,7 +14,7 @@ void main(void) vec4 color = inVertexColor; #endif - color *= emissiveColor; + color *= materialColor; varColor = color; eyeVec = -(mWorldView * inVertexPosition).xyz; diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl index 65acba92a..05134a5f6 100644 --- a/client/shaders/object_shader/opengl_vertex.glsl +++ b/client/shaders/object_shader/opengl_vertex.glsl @@ -1,7 +1,7 @@ uniform mat4 mWorld; uniform vec3 dayLight; uniform float animationTimer; -uniform lowp vec4 emissiveColor; +uniform lowp vec4 materialColor; varying vec3 vNormal; varying vec3 vPosition; @@ -115,7 +115,7 @@ void main(void) vec4 color = inVertexColor; #endif - color *= emissiveColor; + color *= materialColor; // The alpha gives the ratio of sunlight in the incoming light. nightRatio = 1.0 - color.a; diff --git a/client/shaders/stars_shader/opengl_fragment.glsl b/client/shaders/stars_shader/opengl_fragment.glsl index 224032fa3..e991e4f94 100644 --- a/client/shaders/stars_shader/opengl_fragment.glsl +++ b/client/shaders/stars_shader/opengl_fragment.glsl @@ -1,6 +1,6 @@ -uniform lowp vec4 emissiveColor; +uniform lowp vec4 materialColor; void main(void) { - gl_FragColor = emissiveColor; + gl_FragColor = materialColor; } diff --git a/irr/include/EMaterialProps.h b/irr/include/EMaterialProps.h index 6f37c95b8..765084340 100644 --- a/irr/include/EMaterialProps.h +++ b/irr/include/EMaterialProps.h @@ -18,12 +18,6 @@ enum E_MATERIAL_PROP //! Corresponds to SMaterial::PointCloud. EMP_POINTCLOUD = 0x2, - //! Corresponds to SMaterial::GouraudShading. - EMP_GOURAUD_SHADING = 0x4, - - //! Corresponds to SMaterial::Lighting. - EMP_LIGHTING = 0x8, - //! Corresponds to SMaterial::ZBuffer. EMP_ZBUFFER = 0x10, @@ -48,9 +42,6 @@ enum E_MATERIAL_PROP //! Corresponds to SMaterial::FogEnable. EMP_FOG_ENABLE = 0x800, - //! Corresponds to SMaterial::NormalizeNormals. - EMP_NORMALIZE_NORMALS = 0x1000, - //! Corresponds to SMaterialLayer::TextureWrapU, TextureWrapV and //! TextureWrapW. EMP_TEXTURE_WRAP = 0x2000, @@ -61,9 +52,6 @@ enum E_MATERIAL_PROP //! Corresponds to SMaterial::ColorMask. EMP_COLOR_MASK = 0x8000, - //! Corresponds to SMaterial::ColorMaterial. - EMP_COLOR_MATERIAL = 0x10000, - //! Corresponds to SMaterial::UseMipMaps. EMP_USE_MIP_MAPS = 0x20000, diff --git a/irr/include/SMaterial.h b/irr/include/SMaterial.h index 8f24b9984..cceccad78 100644 --- a/irr/include/SMaterial.h +++ b/irr/include/SMaterial.h @@ -194,29 +194,6 @@ enum E_ANTI_ALIASING_MODE EAAM_ALPHA_TO_COVERAGE = 4 }; -//! These flags allow to define the interpretation of vertex color when lighting is enabled -/** Without lighting being enabled the vertex color is the only value defining the fragment color. -Once lighting is enabled, the four values for diffuse, ambient, emissive, and specular take over. -With these flags it is possible to define which lighting factor shall be defined by the vertex color -instead of the lighting factor which is the same for all faces of that material. -The default is to use vertex color for the diffuse value, another pretty common value is to use -vertex color for both diffuse and ambient factor. */ -enum E_COLOR_MATERIAL -{ - //! Don't use vertex color for lighting - ECM_NONE = 0, - //! Use vertex color for diffuse light, this is default - ECM_DIFFUSE, - //! Use vertex color for ambient light - ECM_AMBIENT, - //! Use vertex color for emissive light - ECM_EMISSIVE, - //! Use vertex color for specular light - ECM_SPECULAR, - //! Use vertex color for both diffuse and ambient light - ECM_DIFFUSE_AND_AMBIENT -}; - //! Names for polygon offset direction const c8 *const PolygonOffsetDirectionNames[] = { "Back", @@ -262,16 +239,14 @@ class SMaterial public: //! Default constructor. Creates a solid, lit material with white colors SMaterial() : - MaterialType(EMT_SOLID), AmbientColor(255, 255, 255, 255), - DiffuseColor(255, 255, 255, 255), EmissiveColor(0, 0, 0, 0), - SpecularColor(255, 255, 255, 255), Shininess(0.0f), + MaterialType(EMT_SOLID), ColorParam(0, 0, 0, 0), MaterialTypeParam(0.0f), Thickness(1.0f), ZBuffer(ECFN_LESSEQUAL), - AntiAliasing(EAAM_SIMPLE), ColorMask(ECP_ALL), ColorMaterial(ECM_DIFFUSE), + AntiAliasing(EAAM_SIMPLE), ColorMask(ECP_ALL), BlendOperation(EBO_NONE), BlendFactor(0.0f), PolygonOffsetDepthBias(0.f), PolygonOffsetSlopeScale(0.f), Wireframe(false), PointCloud(false), - GouraudShading(true), Lighting(true), ZWriteEnable(EZW_AUTO), + ZWriteEnable(EZW_AUTO), BackfaceCulling(true), FrontfaceCulling(false), FogEnable(false), - NormalizeNormals(false), UseMipMaps(true) + UseMipMaps(true) { } @@ -281,42 +256,9 @@ public: //! Type of the material. Specifies how everything is blended together E_MATERIAL_TYPE MaterialType; - //! How much ambient light (a global light) is reflected by this material. - /** The default is full white, meaning objects are completely - globally illuminated. Reduce this if you want to see diffuse - or specular light effects. */ - SColor AmbientColor; - - //! How much diffuse light coming from a light source is reflected by this material. - /** The default is full white. */ - SColor DiffuseColor; - - //! Light emitted by this material. Default is to emit no light. - SColor EmissiveColor; - - //! How much specular light (highlights from a light) is reflected. - /** The default is to reflect white specular light. See - SMaterial::Shininess on how to enable specular lights. */ - SColor SpecularColor; - - //! Value affecting the size of specular highlights. - /** A value of 20 is common. If set to 0, no specular - highlights are being used. To activate, simply set the - shininess of a material to a value in the range [0.5;128]: - \code - sceneNode->getMaterial(0).Shininess = 20.0f; - \endcode - - You can change the color of the highlights using - \code - sceneNode->getMaterial(0).SpecularColor.set(255,255,255,255); - \endcode - - The specular color of the dynamic lights - (SLight::SpecularColor) will influence the the highlight color - too, but they are set to a useful value by default when - creating the light scene node.*/ - f32 Shininess; + //! Custom color parameter, can be used by custom shader materials. + // See MainShaderConstantSetter in Minetest. + SColor ColorParam; //! Free parameter, dependent on the material type. /** Mostly ignored, used for example in @@ -344,14 +286,6 @@ public: depth or stencil buffer, or using Red and Green for Stereo rendering. */ u8 ColorMask : 4; - //! Defines the interpretation of vertex color in the lighting equation - /** Values should be chosen from E_COLOR_MATERIAL. - When lighting is enabled, vertex color can be used instead of the - material values for light modulation. This allows to easily change e.g. the - diffuse light behavior of each face. The default, ECM_DIFFUSE, will result in - a very similar rendering as with lighting turned off, just with light shading. */ - u8 ColorMaterial : 3; - //! Store the blend operation of choice /** Values to be chosen from E_BLEND_OPERATION. */ E_BLEND_OPERATION BlendOperation : 4; @@ -392,12 +326,6 @@ public: //! Draw as point cloud or filled triangles? Default: false bool PointCloud : 1; - //! Flat or Gouraud shading? Default: true - bool GouraudShading : 1; - - //! Will this material be lighted? Default: true - bool Lighting : 1; - //! Is the zbuffer writable or is it read-only. Default: EZW_AUTO. /** If this parameter is not EZW_OFF, you probably also want to set ZBuffer to values other than ECFN_DISABLED */ @@ -412,10 +340,6 @@ public: //! Is fog enabled? Default: false bool FogEnable : 1; - //! Should normals be normalized? - /** Always use this if the mesh lit and scaled. Default: false */ - bool NormalizeNormals : 1; - //! Shall mipmaps be used if available /** Sometimes, disabling mipmap usage can be useful. Default: true */ bool UseMipMaps : 1; @@ -486,26 +410,17 @@ public: { bool different = MaterialType != b.MaterialType || - AmbientColor != b.AmbientColor || - DiffuseColor != b.DiffuseColor || - EmissiveColor != b.EmissiveColor || - SpecularColor != b.SpecularColor || - Shininess != b.Shininess || MaterialTypeParam != b.MaterialTypeParam || Thickness != b.Thickness || Wireframe != b.Wireframe || PointCloud != b.PointCloud || - GouraudShading != b.GouraudShading || - Lighting != b.Lighting || ZBuffer != b.ZBuffer || ZWriteEnable != b.ZWriteEnable || BackfaceCulling != b.BackfaceCulling || FrontfaceCulling != b.FrontfaceCulling || FogEnable != b.FogEnable || - NormalizeNormals != b.NormalizeNormals || AntiAliasing != b.AntiAliasing || ColorMask != b.ColorMask || - ColorMaterial != b.ColorMaterial || BlendOperation != b.BlendOperation || BlendFactor != b.BlendFactor || PolygonOffsetDepthBias != b.PolygonOffsetDepthBias || diff --git a/irr/include/SOverrideMaterial.h b/irr/include/SOverrideMaterial.h index c52a55b55..1ae324211 100644 --- a/irr/include/SOverrideMaterial.h +++ b/irr/include/SOverrideMaterial.h @@ -98,12 +98,6 @@ struct SOverrideMaterial case EMP_POINTCLOUD: material.PointCloud = Material.PointCloud; break; - case EMP_GOURAUD_SHADING: - material.GouraudShading = Material.GouraudShading; - break; - case EMP_LIGHTING: - material.Lighting = Material.Lighting; - break; case EMP_ZBUFFER: material.ZBuffer = Material.ZBuffer; break; @@ -140,9 +134,6 @@ struct SOverrideMaterial case EMP_FOG_ENABLE: material.FogEnable = Material.FogEnable; break; - case EMP_NORMALIZE_NORMALS: - material.NormalizeNormals = Material.NormalizeNormals; - break; case EMP_TEXTURE_WRAP: for (u32 i = 0; i < MATERIAL_MAX_TEXTURES; ++i) { if (EnableLayerProps[i]) { @@ -158,9 +149,6 @@ struct SOverrideMaterial case EMP_COLOR_MASK: material.ColorMask = Material.ColorMask; break; - case EMP_COLOR_MATERIAL: - material.ColorMaterial = Material.ColorMaterial; - break; case EMP_USE_MIP_MAPS: material.UseMipMaps = Material.UseMipMaps; break; diff --git a/irr/src/CAnimatedMeshSceneNode.cpp b/irr/src/CAnimatedMeshSceneNode.cpp index c3a71f943..295d408f3 100644 --- a/irr/src/CAnimatedMeshSceneNode.cpp +++ b/irr/src/CAnimatedMeshSceneNode.cpp @@ -258,7 +258,6 @@ void CAnimatedMeshSceneNode::render() // for debug purposes only: if (DebugDataVisible && PassCount == 1) { video::SMaterial debug_mat; - debug_mat.Lighting = false; debug_mat.AntiAliasing = 0; driver->setMaterial(debug_mat); // show normals @@ -280,7 +279,6 @@ void CAnimatedMeshSceneNode::render() } debug_mat.ZBuffer = video::ECFN_DISABLED; - debug_mat.Lighting = false; driver->setMaterial(debug_mat); if (DebugDataVisible & scene::EDS_BBOX) @@ -316,7 +314,6 @@ void CAnimatedMeshSceneNode::render() // show mesh if (DebugDataVisible & scene::EDS_MESH_WIRE_OVERLAY) { - debug_mat.Lighting = false; debug_mat.Wireframe = true; debug_mat.ZBuffer = video::ECFN_DISABLED; driver->setMaterial(debug_mat); diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index 4cd7b1d82..4d78860b2 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -485,7 +485,8 @@ bool CB3DMeshFileLoader::readChunkTRIS(scene::SSkinMeshBuffer *meshBuffer, u32 m video::S3DVertex *Vertex = meshBuffer->getVertex(meshBuffer->getVertexCount() - 1); if (!HasVertexColors) - Vertex->Color = B3dMaterial->Material.DiffuseColor; + Vertex->Color = video::SColorf(B3dMaterial->red, B3dMaterial->green, + B3dMaterial->blue, B3dMaterial->alpha).toSColor(); else if (Vertex->Color.getAlpha() == 255) Vertex->Color.setAlpha((s32)(B3dMaterial->alpha * 255.0f)); @@ -890,23 +891,8 @@ bool CB3DMeshFileLoader::readChunkBRUS() } } - B3dMaterial.Material.DiffuseColor = video::SColorf(B3dMaterial.red, B3dMaterial.green, B3dMaterial.blue, B3dMaterial.alpha).toSColor(); - B3dMaterial.Material.ColorMaterial = video::ECM_NONE; - //------ Material fx ------ - if (B3dMaterial.fx & 1) { // full-bright - B3dMaterial.Material.AmbientColor = video::SColor(255, 255, 255, 255); - B3dMaterial.Material.Lighting = false; - } else - B3dMaterial.Material.AmbientColor = B3dMaterial.Material.DiffuseColor; - - if (B3dMaterial.fx & 2) // use vertex colors instead of brush color - B3dMaterial.Material.ColorMaterial = video::ECM_DIFFUSE_AND_AMBIENT; - - if (B3dMaterial.fx & 4) // flatshaded - B3dMaterial.Material.GouraudShading = false; - if (B3dMaterial.fx & 16) // disable backface culling B3dMaterial.Material.BackfaceCulling = false; @@ -914,8 +900,6 @@ bool CB3DMeshFileLoader::readChunkBRUS() B3dMaterial.Material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; B3dMaterial.Material.ZWriteEnable = video::EZW_OFF; } - - B3dMaterial.Material.Shininess = B3dMaterial.shininess; } B3dStack.erase(B3dStack.size() - 1); diff --git a/irr/src/CBillboardSceneNode.cpp b/irr/src/CBillboardSceneNode.cpp index db75a2af4..1c6d88dae 100644 --- a/irr/src/CBillboardSceneNode.cpp +++ b/irr/src/CBillboardSceneNode.cpp @@ -85,7 +85,6 @@ void CBillboardSceneNode::render() if (DebugDataVisible & scene::EDS_BBOX) { driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); video::SMaterial m; - m.Lighting = false; driver->setMaterial(m); driver->draw3DBox(BBoxSafe, video::SColor(0, 208, 195, 152)); } diff --git a/irr/src/CMeshSceneNode.cpp b/irr/src/CMeshSceneNode.cpp index 7ff6cad55..030e1fd15 100644 --- a/irr/src/CMeshSceneNode.cpp +++ b/irr/src/CMeshSceneNode.cpp @@ -115,7 +115,6 @@ void CMeshSceneNode::render() // for debug purposes only: if (DebugDataVisible && PassCount == 1) { video::SMaterial m; - m.Lighting = false; m.AntiAliasing = 0; driver->setMaterial(m); diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index 91f441a14..c7b296b57 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -104,7 +104,6 @@ CNullDriver::CNullDriver(io::IFileSystem *io, const core::dimension2d &scre FeatureEnabled[i] = true; InitMaterial2D.AntiAliasing = video::EAAM_OFF; - InitMaterial2D.Lighting = false; InitMaterial2D.ZWriteEnable = video::EZW_OFF; InitMaterial2D.ZBuffer = video::ECFN_DISABLED; InitMaterial2D.UseMipMaps = false; @@ -1131,15 +1130,10 @@ void CNullDriver::drawBuffers(const scene::IVertexBuffer *vb, void CNullDriver::drawMeshBufferNormals(const scene::IMeshBuffer *mb, f32 length, SColor color) { const u32 count = mb->getVertexCount(); - const bool normalize = mb->getMaterial().NormalizeNormals; - for (u32 i = 0; i < count; ++i) { - core::vector3df normalizedNormal = mb->getNormal(i); - if (normalize) - normalizedNormal.normalize(); - + core::vector3df normal = mb->getNormal(i); const core::vector3df &pos = mb->getPosition(i); - draw3DLine(pos, pos + (normalizedNormal * length), color); + draw3DLine(pos, pos + (normal * length), color); } } @@ -1306,10 +1300,8 @@ void CNullDriver::runOcclusionQuery(scene::ISceneNode *node, bool visible) OcclusionQueries[index].Run = 0; if (!visible) { SMaterial mat; - mat.Lighting = false; mat.AntiAliasing = 0; mat.ColorMask = ECP_NONE; - mat.GouraudShading = false; mat.ZWriteEnable = EZW_OFF; setMaterial(mat); } diff --git a/irr/src/COBJMeshFileLoader.cpp b/irr/src/COBJMeshFileLoader.cpp index bceba6a90..97e90c322 100644 --- a/irr/src/COBJMeshFileLoader.cpp +++ b/irr/src/COBJMeshFileLoader.cpp @@ -182,7 +182,7 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file) mtlChanged = false; } if (currMtl) - v.Color = currMtl->Meshbuffer->Material.DiffuseColor; + v.Color = video::SColorf(0.8f, 0.8f, 0.8f, 1.0f).toSColor(); // get all vertices data in this face (current line of obj file) const core::stringc wordBuffer = copyLine(bufPtr, bufEnd); diff --git a/irr/src/COBJMeshFileLoader.h b/irr/src/COBJMeshFileLoader.h index 63e768e4f..467392a3e 100644 --- a/irr/src/COBJMeshFileLoader.h +++ b/irr/src/COBJMeshFileLoader.h @@ -43,10 +43,6 @@ private: RecalculateNormals(false) { Meshbuffer = new SMeshBuffer(); - Meshbuffer->Material.Shininess = 0.0f; - Meshbuffer->Material.AmbientColor = video::SColorf(0.2f, 0.2f, 0.2f, 1.0f).toSColor(); - Meshbuffer->Material.DiffuseColor = video::SColorf(0.8f, 0.8f, 0.8f, 1.0f).toSColor(); - Meshbuffer->Material.SpecularColor = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f).toSColor(); } SObjMtl(const SObjMtl &o) : diff --git a/irr/src/COpenGLDriver.cpp b/irr/src/COpenGLDriver.cpp index 837dc05d3..e5f070f8e 100644 --- a/irr/src/COpenGLDriver.cpp +++ b/irr/src/COpenGLDriver.cpp @@ -1840,112 +1840,11 @@ void COpenGLDriver::setBasicRenderStates(const SMaterial &material, const SMater E_OPENGL_FIXED_PIPELINE_STATE tempState = FixedPipelineState; if (resetAllRenderStates || tempState == EOFPS_ENABLE || tempState == EOFPS_DISABLE_TO_ENABLE) { - // material colors - if (resetAllRenderStates || tempState == EOFPS_DISABLE_TO_ENABLE || - lastmaterial.ColorMaterial != material.ColorMaterial) { - switch (material.ColorMaterial) { - case ECM_NONE: - glDisable(GL_COLOR_MATERIAL); - break; - case ECM_DIFFUSE: - glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE); - break; - case ECM_AMBIENT: - glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT); - break; - case ECM_EMISSIVE: - glColorMaterial(GL_FRONT_AND_BACK, GL_EMISSION); - break; - case ECM_SPECULAR: - glColorMaterial(GL_FRONT_AND_BACK, GL_SPECULAR); - break; - case ECM_DIFFUSE_AND_AMBIENT: - glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); - break; - } - if (material.ColorMaterial != ECM_NONE) - glEnable(GL_COLOR_MATERIAL); - } - - if (resetAllRenderStates || tempState == EOFPS_DISABLE_TO_ENABLE || - lastmaterial.AmbientColor != material.AmbientColor || - lastmaterial.DiffuseColor != material.DiffuseColor || - lastmaterial.EmissiveColor != material.EmissiveColor || - lastmaterial.ColorMaterial != material.ColorMaterial) { - GLfloat color[4]; - - const f32 inv = 1.0f / 255.0f; - - if ((material.ColorMaterial != video::ECM_AMBIENT) && - (material.ColorMaterial != video::ECM_DIFFUSE_AND_AMBIENT)) { - color[0] = material.AmbientColor.getRed() * inv; - color[1] = material.AmbientColor.getGreen() * inv; - color[2] = material.AmbientColor.getBlue() * inv; - color[3] = material.AmbientColor.getAlpha() * inv; - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color); - } - - if ((material.ColorMaterial != video::ECM_DIFFUSE) && - (material.ColorMaterial != video::ECM_DIFFUSE_AND_AMBIENT)) { - color[0] = material.DiffuseColor.getRed() * inv; - color[1] = material.DiffuseColor.getGreen() * inv; - color[2] = material.DiffuseColor.getBlue() * inv; - color[3] = material.DiffuseColor.getAlpha() * inv; - glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color); - } - - if (material.ColorMaterial != video::ECM_EMISSIVE) { - color[0] = material.EmissiveColor.getRed() * inv; - color[1] = material.EmissiveColor.getGreen() * inv; - color[2] = material.EmissiveColor.getBlue() * inv; - color[3] = material.EmissiveColor.getAlpha() * inv; - glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, color); - } - } - - if (resetAllRenderStates || tempState == EOFPS_DISABLE_TO_ENABLE || - lastmaterial.SpecularColor != material.SpecularColor || - lastmaterial.Shininess != material.Shininess || - lastmaterial.ColorMaterial != material.ColorMaterial) { - GLfloat color[4] = {0.f, 0.f, 0.f, 1.f}; - const f32 inv = 1.0f / 255.0f; - - glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material.Shininess); - // disable Specular colors if no shininess is set - if ((material.Shininess != 0.0f) && - (material.ColorMaterial != video::ECM_SPECULAR)) { -#ifdef GL_EXT_separate_specular_color - if (FeatureAvailable[IRR_EXT_separate_specular_color]) - glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); -#endif - color[0] = material.SpecularColor.getRed() * inv; - color[1] = material.SpecularColor.getGreen() * inv; - color[2] = material.SpecularColor.getBlue() * inv; - color[3] = material.SpecularColor.getAlpha() * inv; - } -#ifdef GL_EXT_separate_specular_color - else if (FeatureAvailable[IRR_EXT_separate_specular_color]) - glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SINGLE_COLOR); -#endif - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, color); - } - - // shademode - if (resetAllRenderStates || tempState == EOFPS_DISABLE_TO_ENABLE || - lastmaterial.GouraudShading != material.GouraudShading) { - if (material.GouraudShading) - glShadeModel(GL_SMOOTH); - else - glShadeModel(GL_FLAT); - } - - // lighting - if (resetAllRenderStates || tempState == EOFPS_DISABLE_TO_ENABLE || - lastmaterial.Lighting != material.Lighting) { - if (material.Lighting) - glEnable(GL_LIGHTING); - else - glDisable(GL_LIGHTING); + if (resetAllRenderStates || tempState == EOFPS_DISABLE_TO_ENABLE) { + glDisable(GL_COLOR_MATERIAL); + glDisable(GL_LIGHTING); + glShadeModel(GL_SMOOTH); + glDisable(GL_NORMALIZE); } // fog @@ -1957,15 +1856,6 @@ void COpenGLDriver::setBasicRenderStates(const SMaterial &material, const SMater glDisable(GL_FOG); } - // normalization - if (resetAllRenderStates || tempState == EOFPS_DISABLE_TO_ENABLE || - lastmaterial.NormalizeNormals != material.NormalizeNormals) { - if (material.NormalizeNormals) - glEnable(GL_NORMALIZE); - else - glDisable(GL_NORMALIZE); - } - // Set fixed pipeline as active. tempState = EOFPS_ENABLE; } else if (tempState == EOFPS_ENABLE_TO_DISABLE) { @@ -2405,7 +2295,6 @@ void COpenGLDriver::setRenderStates2DMode(bool alpha, bool texture, bool alphaCh } SMaterial currentMaterial = (!OverrideMaterial2DEnabled) ? InitMaterial2D : OverrideMaterial2D; - currentMaterial.Lighting = false; if (texture) { setTransform(ETS_TEXTURE_0, core::IdentityMatrix); diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index 483955805..fc0e6e237 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -109,10 +109,6 @@ bool CXMeshFileLoader::load(io::IReadFile *file) // default material if nothing loaded if (!mesh->Materials.size()) { mesh->Materials.push_back(video::SMaterial()); - mesh->Materials[0].DiffuseColor.set(0xff777777); - mesh->Materials[0].Shininess = 0.f; - mesh->Materials[0].SpecularColor.set(0xff777777); - mesh->Materials[0].EmissiveColor.set(0xff000000); } u32 i; @@ -142,7 +138,7 @@ bool CXMeshFileLoader::load(io::IReadFile *file) if (!mesh->HasVertexColors) { for (u32 j = 0; j < mesh->FaceMaterialIndices.size(); ++j) { for (u32 id = j * 3 + 0; id <= j * 3 + 2; ++id) { - mesh->Vertices[mesh->Indices[id]].Color = mesh->Buffers[mesh->FaceMaterialIndices[j]]->Material.DiffuseColor; + mesh->Vertices[mesh->Indices[id]].Color = 0xff777777; } } } diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 3a921d104..fe9a24758 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -1478,10 +1478,8 @@ void COpenGL3DriverBase::chooseMaterial2D() Material = InitMaterial2D; if (OverrideMaterial2DEnabled) { - OverrideMaterial2D.Lighting = false; OverrideMaterial2D.ZWriteEnable = EZW_OFF; OverrideMaterial2D.ZBuffer = ECFN_DISABLED; // it will be ECFN_DISABLED after merge - OverrideMaterial2D.Lighting = false; Material = OverrideMaterial2D; } diff --git a/irr/src/OpenGL/FixedPipelineRenderer.cpp b/irr/src/OpenGL/FixedPipelineRenderer.cpp index f87220b39..cef446587 100644 --- a/irr/src/OpenGL/FixedPipelineRenderer.cpp +++ b/irr/src/OpenGL/FixedPipelineRenderer.cpp @@ -24,13 +24,7 @@ COpenGL3MaterialBaseCB::COpenGL3MaterialBaseCB() : void COpenGL3MaterialBaseCB::OnSetMaterial(const SMaterial &material) { -#ifdef _DEBUG - if (material.Lighting) - os::Printer::log("Lighted material not supported in unified driver.", ELL_INFORMATION); -#endif - FogEnable = material.FogEnable; - Thickness = (material.Thickness > 0.f) ? material.Thickness : 1.f; } diff --git a/src/client/camera.cpp b/src/client/camera.cpp index f13046848..bf9ec0bd5 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -63,7 +63,7 @@ Camera::Camera(MapDrawControl &draw_control, Client *client, RenderingEngine *re // all other 3D scene nodes and before the GUI. m_wieldmgr = smgr->createNewSceneManager(); m_wieldmgr->addCameraSceneNode(); - m_wieldnode = new WieldMeshSceneNode(m_wieldmgr, -1, false); + m_wieldnode = new WieldMeshSceneNode(m_wieldmgr, -1); m_wieldnode->setItem(ItemStack(), m_client); m_wieldnode->drop(); // m_wieldmgr grabbed it diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 12945ed1b..3cc3564bc 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -1223,7 +1223,6 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, local_material.FrontfaceCulling = material.FrontfaceCulling; } local_material.BlendOperation = material.BlendOperation; - local_material.Lighting = false; driver->setMaterial(local_material); ++material_swaps; } diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 71028768e..a7154c2c7 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -50,7 +50,6 @@ Clouds::Clouds(scene::ISceneManager* mgr, IShaderSource *ssrc, assert(ssrc); m_enable_shaders = g_settings->getBool("enable_shaders"); - m_material.Lighting = false; m_material.BackfaceCulling = true; m_material.FogEnable = true; m_material.AntiAliasing = video::EAAM_SIMPLE; @@ -139,7 +138,7 @@ void Clouds::updateMesh() video::SColorf c_side_2_f(m_color); video::SColorf c_bottom_f(m_color); if (m_enable_shaders) { - // shader mixes the base color, set via EmissiveColor + // shader mixes the base color, set via ColorParam c_top_f = c_side_1_f = c_side_2_f = c_bottom_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); } c_side_1_f.r *= 0.95f; @@ -364,7 +363,7 @@ void Clouds::render() m_material.BackfaceCulling = is3D(); if (m_enable_shaders) - m_material.EmissiveColor = m_color.toSColor(); + m_material.ColorParam = m_color.toSColor(); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); driver->setMaterial(m_material); diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 467c74c42..adec70983 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -186,10 +186,10 @@ static bool logOnce(const std::ostringstream &from, std::ostream &log_to) return true; } -static void setEmissiveColor(scene::ISceneNode *node, video::SColor color) +static void setColorParam(scene::ISceneNode *node, video::SColor color) { for (u32 i = 0; i < node->getMaterialCount(); ++i) - node->getMaterial(i).EmissiveColor = color; + node->getMaterial(i).ColorParam = color; } /* @@ -261,7 +261,6 @@ void TestCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) u16 indices[] = {0,1,2,2,3,0}; buf->append(vertices, 4, indices, 6); // Set material - buf->getMaterial().Lighting = false; buf->getMaterial().BackfaceCulling = false; buf->getMaterial().TextureLayers[0].Texture = tsrc->getTextureForMesh("rat.png"); buf->getMaterial().TextureLayers[0].MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; @@ -648,12 +647,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) auto setMaterial = [this] (video::SMaterial &mat) { mat.MaterialType = m_material_type; - mat.Lighting = false; mat.FogEnable = true; - if (m_enable_shaders) { - mat.GouraudShading = false; - mat.NormalizeNormals = true; - } mat.forEachTexture([] (auto &tex) { tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; tex.MagFilter = video::ETMAGF_NEAREST; @@ -710,7 +704,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) // Set material setMaterial(buf->getMaterial()); if (m_enable_shaders) { - buf->getMaterial().EmissiveColor = c; + buf->getMaterial().ColorParam = c; } // Add to mesh @@ -736,7 +730,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) // Set material setMaterial(buf->getMaterial()); if (m_enable_shaders) { - buf->getMaterial().EmissiveColor = c; + buf->getMaterial().ColorParam = c; } // Add to mesh @@ -936,7 +930,7 @@ void GenericCAO::setNodeLight(const video::SColor &light_color) auto *node = getSceneNode(); if (!node) return; - setEmissiveColor(node, light_color); + setColorParam(node, light_color); } else { if (m_meshnode) { setMeshColor(m_meshnode->getMesh(), light_color); @@ -1366,15 +1360,6 @@ void GenericCAO::updateTextures(std::string mod) material.MaterialTypeParam = m_material_type_param; material.setTexture(0, tsrc->getTextureForMesh(texturestring)); - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. - if (!m_prop.colors.empty()) { - material.AmbientColor = m_prop.colors[0]; - material.DiffuseColor = m_prop.colors[0]; - material.SpecularColor = m_prop.colors[0]; - } - material.forEachTexture([=] (auto &tex) { setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, use_anisotropic_filter); @@ -1417,17 +1402,6 @@ void GenericCAO::updateTextures(std::string mod) use_anisotropic_filter); }); } - for (u32 i = 0; i < m_prop.colors.size() && - i < m_animated_meshnode->getMaterialCount(); ++i) - { - video::SMaterial &material = m_animated_meshnode->getMaterial(i); - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. - material.AmbientColor = m_prop.colors[i]; - material.DiffuseColor = m_prop.colors[i]; - material.SpecularColor = m_prop.colors[i]; - } } } @@ -1445,20 +1419,9 @@ void GenericCAO::updateTextures(std::string mod) video::SMaterial &material = m_meshnode->getMaterial(i); material.MaterialType = m_material_type; material.MaterialTypeParam = m_material_type_param; - material.Lighting = false; material.setTexture(0, tsrc->getTextureForMesh(texturestring)); material.getTextureMatrix(0).makeIdentity(); - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. - if(m_prop.colors.size() > i) - { - material.AmbientColor = m_prop.colors[i]; - material.DiffuseColor = m_prop.colors[i]; - material.SpecularColor = m_prop.colors[i]; - } - material.forEachTexture([=] (auto &tex) { setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, use_anisotropic_filter); @@ -1475,15 +1438,6 @@ void GenericCAO::updateTextures(std::string mod) auto &material = m_meshnode->getMaterial(0); material.setTexture(0, tsrc->getTextureForMesh(tname)); - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. - if(!m_prop.colors.empty()) { - material.AmbientColor = m_prop.colors[0]; - material.DiffuseColor = m_prop.colors[0]; - material.SpecularColor = m_prop.colors[0]; - } - material.forEachTexture([=] (auto &tex) { setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, use_anisotropic_filter); @@ -1500,19 +1454,6 @@ void GenericCAO::updateTextures(std::string mod) auto &material = m_meshnode->getMaterial(1); material.setTexture(0, tsrc->getTextureForMesh(tname)); - // This allows setting per-material colors. However, until a real lighting - // system is added, the code below will have no effect. Once MineTest - // has directional lighting, it should work automatically. - if (m_prop.colors.size() >= 2) { - material.AmbientColor = m_prop.colors[1]; - material.DiffuseColor = m_prop.colors[1]; - material.SpecularColor = m_prop.colors[1]; - } else if (!m_prop.colors.empty()) { - material.AmbientColor = m_prop.colors[0]; - material.DiffuseColor = m_prop.colors[0]; - material.SpecularColor = m_prop.colors[0]; - } - material.forEachTexture([=] (auto &tex) { setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, use_anisotropic_filter); diff --git a/src/client/content_cso.cpp b/src/client/content_cso.cpp index 821c2507d..733044b23 100644 --- a/src/client/content_cso.cpp +++ b/src/client/content_cso.cpp @@ -40,7 +40,6 @@ public: video::ITexture *tex = env->getGameDef()->tsrc()->getTextureForMesh("smoke_puff.png"); m_spritenode->forEachMaterial([tex] (auto &mat) { mat.TextureLayers[0].Texture = tex; - mat.Lighting = false; mat.TextureLayers[0].MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; mat.TextureLayers[0].MagFilter = video::ETMAGF_NEAREST; mat.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 0a5db6858..e4c06b542 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -99,7 +99,6 @@ Hud::Hud(Client *client, LocalPlayer *player, // Initialize m_selection_material - m_selection_material.Lighting = false; if (g_settings->getBool("enable_shaders")) { IShaderSource *shdrsrc = client->getShaderSource(); @@ -121,7 +120,6 @@ Hud::Hud(Client *client, LocalPlayer *player, } // Initialize m_block_bounds_material - m_block_bounds_material.Lighting = false; if (g_settings->getBool("enable_shaders")) { IShaderSource *shdrsrc = client->getShaderSource(); auto shader_id = shdrsrc->getShader("default_shader", TILE_MATERIAL_ALPHA); @@ -155,7 +153,6 @@ Hud::Hud(Client *client, LocalPlayer *player, indices[4] = 3; indices[5] = 0; - b->getMaterial().Lighting = false; b->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; b->setHardwareMappingHint(scene::EHM_STATIC); } @@ -1205,7 +1202,6 @@ void drawItemStack( video::SMaterial &material = buf->getMaterial(); material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - material.Lighting = false; driver->setMaterial(material); driver->drawMeshBuffer(buf); } diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 9c0caa9d0..1933baddf 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -736,7 +736,6 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs // Create material video::SMaterial material; - material.Lighting = false; material.BackfaceCulling = true; material.FogEnable = true; material.setTexture(0, p.layer.texture); diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 1e32fcfd1..9e66404e3 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -99,7 +99,6 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale) scene::IMeshBuffer *buf = new scene::SMeshBuffer(); buf->append(vertices + 4 * i, 4, indices, 6); // Set default material - buf->getMaterial().Lighting = false; buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; buf->getMaterial().forEachTexture([] (auto &tex) { tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; @@ -401,7 +400,6 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector &boxes, for (u16 j = 0; j < 6; j++) { scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - buf->getMaterial().Lighting = false; buf->getMaterial().forEachTexture([] (auto &tex) { tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; tex.MagFilter = video::ETMAGF_NEAREST; diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index b2fc0882d..4f3c81f4f 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -612,7 +612,6 @@ void Minimap::drawMinimap(core::rect rect) tex.MinFilter = video::ETMINF_LINEAR_MIPMAP_LINEAR; tex.MagFilter = video::ETMAGF_LINEAR; }); - material.Lighting = false; material.TextureLayers[0].Texture = minimap_texture; material.TextureLayers[1].Texture = data->heightmap_texture; diff --git a/src/client/particles.cpp b/src/client/particles.cpp index c19282424..1eab93579 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -989,7 +989,6 @@ video::SMaterial ParticleManager::getMaterialForParticle(const ClientParticleTex video::SMaterial material; // Texture - material.Lighting = false; material.BackfaceCulling = false; material.FogEnable = true; material.forEachTexture([] (auto &tex) { diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 242fda81c..f43bc27dc 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -218,15 +218,15 @@ class MainShaderConstantSetter : public IShaderConstantSetter CachedVertexShaderSetting m_texture{"mTexture"}; // commonly used way to pass material color to shader - video::SColor m_emissive_color; - CachedPixelShaderSetting m_emissive_color_setting{"emissiveColor"}; + video::SColor m_material_color; + CachedPixelShaderSetting m_material_color_setting{"materialColor"}; public: ~MainShaderConstantSetter() = default; virtual void onSetMaterial(const video::SMaterial& material) override { - m_emissive_color = material.EmissiveColor; + m_material_color = material.ColorParam; } virtual void onSetConstants(video::IMaterialRendererServices *services) override @@ -254,8 +254,8 @@ public: m_texture.set(texture, services); } - video::SColorf emissive_color(m_emissive_color); - m_emissive_color_setting.set(emissive_color, services); + video::SColorf colorf(m_material_color); + m_material_color_setting.set(colorf, services); } }; diff --git a/src/client/shadows/shadowsScreenQuad.cpp b/src/client/shadows/shadowsScreenQuad.cpp index f0eb9ab4a..d1713578d 100644 --- a/src/client/shadows/shadowsScreenQuad.cpp +++ b/src/client/shadows/shadowsScreenQuad.cpp @@ -23,7 +23,6 @@ with this program; if not, write to the Free Software Foundation, Inc., shadowScreenQuad::shadowScreenQuad() { Material.Wireframe = false; - Material.Lighting = false; video::SColor color(0x0); Vertices[0] = video::S3DVertex( diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 50ef56498..65577418e 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -39,7 +39,6 @@ using namespace irr::core; static video::SMaterial baseMaterial() { video::SMaterial mat; - mat.Lighting = false; mat.ZBuffer = video::ECFN_DISABLED; mat.ZWriteEnable = video::EZW_OFF; mat.AntiAliasing = 0; @@ -95,7 +94,6 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade for (int i = 5; i < 11; i++) { m_materials[i] = baseMaterial(); - m_materials[i].Lighting = true; m_materials[i].MaterialType = video::EMT_SOLID; } @@ -169,7 +167,8 @@ void Sky::render() video::SColor texel_color (255, texel->getRed(), texel->getGreen(), texel->getBlue()); m_sun_tonemap->unlock(); - m_materials[3].EmissiveColor = texel_color; + // Only accessed by our code later, not used by a shader + m_materials[3].ColorParam = texel_color; } if (m_moon_tonemap) { @@ -178,7 +177,8 @@ void Sky::render() video::SColor texel_color (255, texel->getRed(), texel->getGreen(), texel->getBlue()); m_moon_tonemap->unlock(); - m_materials[4].EmissiveColor = texel_color; + // Only accessed by our code later, not used by a shader + m_materials[4].ColorParam = texel_color; } const f32 t = 1.0f; @@ -465,11 +465,11 @@ void Sky::update(float time_of_day, float time_brightness, // which keeps previous behavior. if (m_sun_tonemap && m_default_tint) { pointcolor_sun_f.r = pointcolor_light * - (float)m_materials[3].EmissiveColor.getRed() / 255; + (float)m_materials[3].ColorParam.getRed() / 255; pointcolor_sun_f.b = pointcolor_light * - (float)m_materials[3].EmissiveColor.getBlue() / 255; + (float)m_materials[3].ColorParam.getBlue() / 255; pointcolor_sun_f.g = pointcolor_light * - (float)m_materials[3].EmissiveColor.getGreen() / 255; + (float)m_materials[3].ColorParam.getGreen() / 255; } else if (!m_default_tint) { pointcolor_sun_f = m_sky_params.fog_sun_tint; } else { @@ -498,11 +498,11 @@ void Sky::update(float time_of_day, float time_brightness, } if (m_moon_tonemap && m_default_tint) { pointcolor_moon_f.r = pointcolor_light * - (float)m_materials[4].EmissiveColor.getRed() / 255; + (float)m_materials[4].ColorParam.getRed() / 255; pointcolor_moon_f.b = pointcolor_light * - (float)m_materials[4].EmissiveColor.getBlue() / 255; + (float)m_materials[4].ColorParam.getBlue() / 255; pointcolor_moon_f.g = pointcolor_light * - (float)m_materials[4].EmissiveColor.getGreen() / 255; + (float)m_materials[4].ColorParam.getGreen() / 255; } video::SColor pointcolor_sun = pointcolor_sun_f.toSColor(); @@ -603,11 +603,8 @@ void Sky::draw_sun(video::IVideoDriver *driver, const video::SColor &suncolor, // Another magic number that contributes to the ratio 1.57 sun/moon size // difference. float d = (sunsize * 1.7) * m_sun_params.scale; - video::SColor c; - if (m_sun_tonemap) - c = video::SColor(0, 0, 0, 0); - else - c = video::SColor(255, 255, 255, 255); + video::SColor c = m_sun_tonemap ? m_materials[3].ColorParam : + video::SColor(255, 255, 255, 255); draw_sky_body(vertices, -d, d, c); place_sky_body(vertices, 90, wicked_time_of_day * 360 - 90); driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); @@ -660,11 +657,8 @@ void Sky::draw_moon(video::IVideoDriver *driver, const video::SColor &mooncolor, // Another magic number that contributes to the ratio 1.57 sun/moon size // difference. float d = (moonsize * 1.9) * m_moon_params.scale; - video::SColor c; - if (m_moon_tonemap) - c = video::SColor(0, 0, 0, 0); - else - c = video::SColor(255, 255, 255, 255); + video::SColor c = m_sun_tonemap ? m_materials[4].ColorParam : + video::SColor(255, 255, 255, 255); draw_sky_body(vertices, -d, d, c); place_sky_body(vertices, -90, wicked_time_of_day * 360 - 90); driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); @@ -689,7 +683,7 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day) if (color.a <= 0.0f) // Stars are only drawn when not fully transparent return; if (m_enable_shaders) - m_materials[0].EmissiveColor = color.toSColor(); + m_materials[0].ColorParam = color.toSColor(); else setMeshBufferColor(m_stars.get(), color.toSColor()); @@ -745,7 +739,6 @@ void Sky::setSunTexture(const std::string &sun_texture, m_sun_params.tonemap = sun_tonemap; m_sun_tonemap = tsrc->isKnownSourceImage(sun_tonemap) ? tsrc->getTexture(sun_tonemap) : nullptr; - m_materials[3].Lighting = !!m_sun_tonemap; if (m_sun_params.texture == sun_texture && !m_first_update) return; @@ -765,7 +758,6 @@ void Sky::setSunTexture(const std::string &sun_texture, m_materials[3].setTexture(0, m_sun_texture); m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; disableTextureFiltering(m_materials[3]); - m_materials[3].Lighting = !!m_sun_tonemap; } } @@ -789,7 +781,6 @@ void Sky::setMoonTexture(const std::string &moon_texture, m_moon_params.tonemap = moon_tonemap; m_moon_tonemap = tsrc->isKnownSourceImage(moon_tonemap) ? tsrc->getTexture(moon_tonemap) : nullptr; - m_materials[4].Lighting = !!m_moon_tonemap; if (m_moon_params.texture == moon_texture && !m_first_update) return; @@ -809,7 +800,6 @@ void Sky::setMoonTexture(const std::string &moon_texture, m_materials[4].setTexture(0, m_moon_texture); m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; disableTextureFiltering(m_materials[4]); - m_materials[4].Lighting = !!m_moon_tonemap; } } diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 148130606..e66214ae6 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -194,10 +194,9 @@ private: static ExtrusionMeshCache *g_extrusion_mesh_cache = nullptr; -WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool lighting): +WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id): scene::ISceneNode(mgr->getRootSceneNode(), mgr, id), - m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF), - m_lighting(lighting) + m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF) { m_enable_shaders = g_settings->getBool("enable_shaders"); m_anisotropic_filter = g_settings->getBool("anisotropic_filter"); @@ -390,8 +389,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che // overlay is white, if present m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); // initialize the color - if (!m_lighting) - setColor(video::SColor(0xFFFFFFFF)); + setColor(video::SColor(0xFFFFFFFF)); return; } @@ -468,8 +466,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che } // initialize the color - if (!m_lighting) - setColor(video::SColor(0xFFFFFFFF)); + setColor(video::SColor(0xFFFFFFFF)); return; } else { const std::string inventory_image = item.getInventoryImage(idef); @@ -485,8 +482,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); // initialize the color - if (!m_lighting) - setColor(video::SColor(0xFFFFFFFF)); + setColor(video::SColor(0xFFFFFFFF)); return; } @@ -496,7 +492,6 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che void WieldMeshSceneNode::setColor(video::SColor c) { - assert(!m_lighting); scene::IMesh *mesh = m_meshnode->getMesh(); if (!mesh) return; @@ -535,7 +530,7 @@ void WieldMeshSceneNode::setNodeLightColor(video::SColor color) if (m_enable_shaders) { for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) { video::SMaterial &material = m_meshnode->getMaterial(i); - material.EmissiveColor = color; + material.ColorParam = color; } } else { setColor(color); @@ -565,11 +560,6 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) mesh->setHardwareMappingHint(scene::EHM_DYNAMIC); } - m_meshnode->forEachMaterial([this] (auto &mat) { - mat.Lighting = m_lighting; - // need to normalize normals when lighting is enabled (because of setScale()) - mat.NormalizeNormals = m_lighting; - }); m_meshnode->setVisible(true); } @@ -667,7 +657,6 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) tex.MagFilter = video::ETMAGF_NEAREST; }); material.BackfaceCulling = cull_backface; - material.Lighting = false; } rotateMeshXZby(mesh, -45); @@ -720,7 +709,6 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, tex.MagFilter = video::ETMAGF_NEAREST; }); material.BackfaceCulling = true; - material.Lighting = false; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; material.MaterialTypeParam = 0.5f; } diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index e2c6cd445..08240c8ef 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -104,7 +104,7 @@ struct ItemMesh class WieldMeshSceneNode : public scene::ISceneNode { public: - WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id = -1, bool lighting = false); + WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id = -1); virtual ~WieldMeshSceneNode(); void setCube(const ContentFeatures &f, v3f wield_scale); @@ -132,9 +132,6 @@ private: scene::IMeshSceneNode *m_meshnode = nullptr; video::E_MATERIAL_TYPE m_material_type; - // True if SMaterial::Lighting should be enabled. - bool m_lighting; - bool m_enable_shaders; bool m_anisotropic_filter; bool m_bilinear_filter; diff --git a/src/gui/guiScene.cpp b/src/gui/guiScene.cpp index a26cfa93f..9293ebe22 100644 --- a/src/gui/guiScene.cpp +++ b/src/gui/guiScene.cpp @@ -67,7 +67,6 @@ void GUIScene::setTexture(u32 idx, video::ITexture *texture) material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; material.MaterialTypeParam = 0.5f; material.TextureLayers[0].Texture = texture; - material.Lighting = false; material.FogEnable = true; material.TextureLayers[0].MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; material.TextureLayers[0].MagFilter = video::ETMAGF_NEAREST; diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index 4f5f52b4e..022ab46b0 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -1098,13 +1098,9 @@ core::array CGUITTFont::addTextSceneNode(const wchar_t* text // the default font material SMaterial mat; - mat.Lighting = true; mat.ZWriteEnable = video::EZW_OFF; - mat.NormalizeNormals = true; - mat.ColorMaterial = video::ECM_NONE; mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID; mat.MaterialTypeParam = 0.01f; - mat.DiffuseColor = color; wchar_t current_char = 0, previous_char = 0; u32 n = 0; From 6d01ed5d74ff0a911416d5ceaa17df22787c88bc Mon Sep 17 00:00:00 2001 From: Desour Date: Mon, 10 Jul 2023 00:00:00 +0200 Subject: [PATCH 117/200] irr_ptr: Allow to use with forward-declared types Also add [[nodiscard]] to ::grab() (because similar named irr_ptr::grab() returns void). And use new std::is_convertible_v. --- src/irr_ptr.h | 30 ++++++++++++++++-------------- src/unittest/test_irrptr.cpp | 1 + 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/irr_ptr.h b/src/irr_ptr.h index fc4a0f558..48717976b 100644 --- a/src/irr_ptr.h +++ b/src/irr_ptr.h @@ -20,8 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include #include -#include "irrlichttypes.h" -#include "IReferenceCounted.h" +namespace irr { class IReferenceCounted; } /** Shared pointer for IrrLicht objects. * @@ -37,15 +36,13 @@ with this program; if not, write to the Free Software Foundation, Inc., * from such object is a bug and may lead to a crash. Indirect construction * is possible though; see the @c grab free function for details and use cases. */ -template ::value>::type> +template class irr_ptr { ReferenceCounted *value = nullptr; public: - irr_ptr() {} + irr_ptr() noexcept = default; irr_ptr(std::nullptr_t) noexcept {} @@ -53,15 +50,15 @@ public: irr_ptr(irr_ptr &&b) noexcept { reset(b.release()); } - template ::value>::type> + template , bool> = true> irr_ptr(const irr_ptr &b) noexcept { grab(b.get()); } - template ::value>::type> + template , bool> = true> irr_ptr(irr_ptr &&b) noexcept { reset(b.release()); @@ -88,16 +85,16 @@ public: return *this; } - template ::value>::type> + template , bool> = true> irr_ptr &operator=(const irr_ptr &b) noexcept { grab(b.get()); return *this; } - template ::value>::type> + template , bool> = true> irr_ptr &operator=(irr_ptr &&b) noexcept { reset(b.release()); @@ -128,6 +125,8 @@ public: */ void reset(ReferenceCounted *object = nullptr) noexcept { + static_assert(std::is_base_of_v, + "Class is not an IReferenceCounted"); if (value) value->drop(); value = object; @@ -138,6 +137,8 @@ public: */ void grab(ReferenceCounted *object) noexcept { + static_assert(std::is_base_of_v, + "Class is not an IReferenceCounted"); if (object) object->grab(); reset(object); @@ -152,6 +153,7 @@ public: * in this function and decreased when the returned pointer is destroyed. */ template +[[nodiscard]] irr_ptr grab(ReferenceCounted *object) noexcept { irr_ptr ptr; diff --git a/src/unittest/test_irrptr.cpp b/src/unittest/test_irrptr.cpp index befeefc73..61d1e302c 100644 --- a/src/unittest/test_irrptr.cpp +++ b/src/unittest/test_irrptr.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "exceptions.h" #include "irr_ptr.h" +#include "IReferenceCounted.h" class TestIrrPtr : public TestBase { From e3efaa1733703224a6f703b1102a96d0b74681d5 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 17 Sep 2024 10:02:34 +0200 Subject: [PATCH 118/200] Move irr_ptr.h too irr/include/ --- {src => irr/include}/irr_ptr.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {src => irr/include}/irr_ptr.h (100%) diff --git a/src/irr_ptr.h b/irr/include/irr_ptr.h similarity index 100% rename from src/irr_ptr.h rename to irr/include/irr_ptr.h From ecf8c7696a20c59b35709ae87d09972d2afedb0d Mon Sep 17 00:00:00 2001 From: Desour Date: Mon, 10 Jul 2023 00:00:00 +0200 Subject: [PATCH 119/200] Use irr_ptr for ClientEnvironment::m_map --- src/client/client.cpp | 2 +- src/client/clientenvironment.cpp | 11 +++++------ src/client/clientenvironment.h | 5 +++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index dedbebbb4..b14ef7004 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -120,7 +120,7 @@ Client::Client( m_rendering_engine(rendering_engine), m_mesh_update_manager(std::make_unique(this)), m_env( - new ClientMap(this, rendering_engine, control, 666), + make_irr(this, rendering_engine, control, 666), tsrc, this ), m_particle_manager(std::make_unique(&m_env)), diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index f1ec14d3b..08c5586ea 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -43,10 +43,10 @@ with this program; if not, write to the Free Software Foundation, Inc., ClientEnvironment */ -ClientEnvironment::ClientEnvironment(ClientMap *map, +ClientEnvironment::ClientEnvironment(irr_ptr map, ITextureSource *texturesource, Client *client): Environment(client), - m_map(map), + m_map(std::move(map)), m_texturesource(texturesource), m_client(client) { @@ -60,18 +60,17 @@ ClientEnvironment::~ClientEnvironment() delete simple_object; } - // Drop/delete map - m_map->drop(); + m_map.reset(); delete m_local_player; } -Map & ClientEnvironment::getMap() +Map &ClientEnvironment::getMap() { return *m_map; } -ClientMap & ClientEnvironment::getClientMap() +ClientMap &ClientEnvironment::getClientMap() { return *m_map; } diff --git a/src/client/clientenvironment.h b/src/client/clientenvironment.h index bdb8b9726..db31e69f2 100644 --- a/src/client/clientenvironment.h +++ b/src/client/clientenvironment.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "util/numeric.h" // IntervalLimiter #include "activeobjectmgr.h" // client::ActiveObjectMgr +#include "irr_ptr.h" #include #ifdef SERVER @@ -66,7 +67,7 @@ typedef std::unordered_map ClientActiveObjectMap; class ClientEnvironment : public Environment { public: - ClientEnvironment(ClientMap *map, ITextureSource *texturesource, Client *client); + ClientEnvironment(irr_ptr map, ITextureSource *texturesource, Client *client); ~ClientEnvironment(); Map & getMap(); @@ -151,7 +152,7 @@ public: u64 getFrameTimeDelta() const { return m_frame_dtime; } private: - ClientMap *m_map; + irr_ptr m_map; LocalPlayer *m_local_player = nullptr; ITextureSource *m_texturesource; Client *m_client; From 37b374cb92e0a73d5dd97262d8b5a12471380714 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 11 Jul 2023 00:00:00 +0200 Subject: [PATCH 120/200] Use irr_ptr for Game::clouds --- src/client/game.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 6e01f2ffe..95f67c3a5 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -893,7 +893,7 @@ private: GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop() MapDrawControl *draw_control = nullptr; Camera *camera = nullptr; - Clouds *clouds = nullptr; // Free using ->Drop() + irr_ptr clouds; Sky *sky = nullptr; // Free using ->Drop() Hud *hud = nullptr; Minimap *mapper = nullptr; @@ -1263,8 +1263,7 @@ void Game::shutdown() if (m_shutdown_progress == 0.0f) showOverlayMessage(N_("Shutting down..."), 0, 0); - if (clouds) - clouds->drop(); + clouds.reset(); if (gui_chat_console) gui_chat_console->drop(); @@ -1524,7 +1523,7 @@ bool Game::createClient(const GameStartData &start_data) /* Clouds */ if (m_cache_enable_clouds) - clouds = new Clouds(smgr, shader_src, -1, rand()); + clouds = make_irr(smgr, shader_src, -1, rand()); /* Skybox */ From 17c041a65c91611442a8fa75bbb613ba2761ba42 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 11 Jul 2023 00:00:00 +0200 Subject: [PATCH 121/200] Use irr_ptr for Game::gui_chat_console --- src/client/game.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 95f67c3a5..1dcaa225b 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -890,7 +890,7 @@ private: QuicktuneShortcutter *quicktune = nullptr; std::unique_ptr m_game_ui; - GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop() + irr_ptr gui_chat_console; MapDrawControl *draw_control = nullptr; Camera *camera = nullptr; irr_ptr clouds; @@ -1265,8 +1265,7 @@ void Game::shutdown() clouds.reset(); - if (gui_chat_console) - gui_chat_console->drop(); + gui_chat_console.reset(); if (sky) sky->drop(); @@ -1581,7 +1580,7 @@ bool Game::initGui() chat_backend->applySettings(); // Chat backend and console - gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), + gui_chat_console = make_irr(guienv, guienv->getRootGUIElement(), -1, chat_backend, client, &g_menumgr); if (g_settings->getBool("touch_controls")) { @@ -2037,7 +2036,7 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times, void Game::processUserInput(f32 dtime) { // Reset input if window not active or some menu is active - if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) { + if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console.get())) { if (m_game_focused) { m_game_focused = false; infostream << "Game lost focus" << std::endl; @@ -2060,7 +2059,7 @@ void Game::processUserInput(f32 dtime) m_game_focused = true; } - if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) { + if (!guienv->hasFocus(gui_chat_console.get()) && gui_chat_console->isOpen()) { gui_chat_console->closeConsoleAtOnce(); } @@ -4217,7 +4216,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, updateShadows(); } - m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime); + m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, + gui_chat_console.get(), dtime); /* make sure menu is on top From b93ae33f8559258b042acec35fcb90bbfeb0b5de Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 11 Jul 2023 00:00:00 +0200 Subject: [PATCH 122/200] Use irr_ptr for Game::sky --- src/client/game.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 1dcaa225b..9022f4acc 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -595,7 +595,8 @@ public: m_client(client) {} - void setSky(Sky *sky) { + void setSky(Sky *sky) + { m_sky = sky; for (GameGlobalShaderConstantSetter *ggscs : created_nosky) { ggscs->setSky(m_sky); @@ -894,7 +895,7 @@ private: MapDrawControl *draw_control = nullptr; Camera *camera = nullptr; irr_ptr clouds; - Sky *sky = nullptr; // Free using ->Drop() + irr_ptr sky; Hud *hud = nullptr; Minimap *mapper = nullptr; @@ -1267,8 +1268,7 @@ void Game::shutdown() gui_chat_console.reset(); - if (sky) - sky->drop(); + sky.reset(); /* cleanup menus */ while (g_menumgr.menuCount() > 0) { @@ -1526,8 +1526,8 @@ bool Game::createClient(const GameStartData &start_data) /* Skybox */ - sky = new Sky(-1, m_rendering_engine, texture_src, shader_src); - scsf->setSky(sky); + sky = make_irr(-1, m_rendering_engine, texture_src, shader_src); + scsf->setSky(sky.get()); /* Pre-calculated values */ From 2b2f2dee201bef2e89c54fa54df507f0835d0295 Mon Sep 17 00:00:00 2001 From: Desour Date: Tue, 11 Jul 2023 00:00:00 +0200 Subject: [PATCH 123/200] Use make_irr instead of new + drop() in Game::handleCallbacks --- src/client/game.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 9022f4acc..33475f338 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1871,26 +1871,26 @@ inline bool Game::handleCallbacks() } if (g_gamecallback->changepassword_requested) { - (new GUIPasswordChange(guienv, guiroot, -1, - &g_menumgr, client, texture_src))->drop(); + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, client, texture_src); g_gamecallback->changepassword_requested = false; } if (g_gamecallback->changevolume_requested) { - (new GUIVolumeChange(guienv, guiroot, -1, - &g_menumgr, texture_src))->drop(); + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, texture_src); g_gamecallback->changevolume_requested = false; } if (g_gamecallback->keyconfig_requested) { - (new GUIKeyChangeMenu(guienv, guiroot, -1, - &g_menumgr, texture_src))->drop(); + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, texture_src); g_gamecallback->keyconfig_requested = false; } if (!g_gamecallback->show_open_url_dialog.empty()) { - (new GUIOpenURLMenu(guienv, guiroot, -1, - &g_menumgr, texture_src, g_gamecallback->show_open_url_dialog))->drop(); + (void)make_irr(guienv, guiroot, -1, + &g_menumgr, texture_src, g_gamecallback->show_open_url_dialog); g_gamecallback->show_open_url_dialog.clear(); } From 9827f9df1bbefe636401e701c47d10d146a5a119 Mon Sep 17 00:00:00 2001 From: Desour Date: Fri, 15 Sep 2023 00:00:00 +0200 Subject: [PATCH 124/200] Use irr_ptr for MapBlockMesh::m_mesh --- src/client/mapblock_mesh.cpp | 10 +++++----- src/client/mapblock_mesh.h | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 1933baddf..071b03132 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -615,7 +615,7 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs ZoneScoped; for (auto &m : m_mesh) - m = new scene::SMesh(); + m = make_irr(); m_enable_shaders = data->m_use_shaders; auto mesh_grid = client->getMeshGrid(); @@ -663,7 +663,7 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs m_bounding_radius = std::sqrt(collector.m_bounding_radius_sq); for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { - scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer]; + scene::SMesh *mesh = static_cast(m_mesh[layer].get()); for(u32 i = 0; i < collector.prebuffers[layer].size(); i++) { @@ -793,10 +793,10 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offs MapBlockMesh::~MapBlockMesh() { size_t sz = 0; - for (scene::IMesh *m : m_mesh) { + for (auto &&m : m_mesh) { for (u32 i = 0; i < m->getMeshBufferCount(); i++) sz += m->getMeshBuffer(i)->getSize(); - m->drop(); + m.reset(); } for (MinimapMapblock *block : m_minimap_mapblocks) delete block; @@ -865,7 +865,7 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, get_sunlight_color(&day_color, daynight_ratio); for (auto &daynight_diff : m_daynight_diffs) { - auto *mesh = m_mesh[daynight_diff.first.first]; + auto *mesh = m_mesh[daynight_diff.first.first].get(); mesh->setDirty(scene::EBT_VERTEX); // force reload to VBO scene::IMeshBuffer *buf = mesh-> getMeshBuffer(daynight_diff.first.second); diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index d2c651525..c52df5ed3 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -193,12 +193,12 @@ public: scene::IMesh *getMesh() { - return m_mesh[0]; + return m_mesh[0].get(); } scene::IMesh *getMesh(u8 layer) { - return m_mesh[layer]; + return m_mesh[layer].get(); } std::vector moveMinimapMapblocks() @@ -242,7 +242,7 @@ private: TileLayer tile; }; - scene::IMesh *m_mesh[MAX_TILE_LAYERS]; + irr_ptr m_mesh[MAX_TILE_LAYERS]; std::vector m_minimap_mapblocks; ITextureSource *m_tsrc; IShaderSource *m_shdrsrc; From 24efd7dc9146c61fb090420e7619b7c2000fddcf Mon Sep 17 00:00:00 2001 From: Desour Date: Fri, 15 Sep 2023 00:00:00 +0200 Subject: [PATCH 125/200] Use smart ptrs for Minimap's member vars --- src/client/minimap.cpp | 40 +++++++++++++++++++--------------------- src/client/minimap.h | 11 ++++++----- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 4f3c81f4f..13dca5fe9 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -201,7 +201,7 @@ Minimap::Minimap(Client *client) addMode(MINIMAP_TYPE_RADAR, 128); // Initialize minimap data - data = new MinimapData; + data = std::make_unique(); data->map_invalidated = true; data->minimap_shape_round = g_settings->getBool("minimap_shape_round"); @@ -209,11 +209,11 @@ Minimap::Minimap(Client *client) setModeIndex(0); // Create mesh buffer for minimap - m_meshbuffer = getMinimapMeshBuffer(); + m_meshbuffer = createMinimapMeshBuffer(); // Initialize and start thread - m_minimap_update_thread = new MinimapUpdateThread(); - m_minimap_update_thread->data = data; + m_minimap_update_thread = std::make_unique(); + m_minimap_update_thread->data = data.get(); m_minimap_update_thread->start(); } @@ -222,7 +222,7 @@ Minimap::~Minimap() m_minimap_update_thread->stop(); m_minimap_update_thread->wait(); - m_meshbuffer->drop(); + m_meshbuffer.reset(); if (data->minimap_mask_round) data->minimap_mask_round->drop(); @@ -232,12 +232,10 @@ Minimap::~Minimap() driver->removeTexture(data->texture); driver->removeTexture(data->heightmap_texture); - for (MinimapMarker *m : m_markers) - delete m; m_markers.clear(); - delete data; - delete m_minimap_update_thread; + data.reset(); + m_minimap_update_thread.reset(); } void Minimap::addBlock(v3s16 pos, MinimapMapblock *data) @@ -552,9 +550,9 @@ v3f Minimap::getYawVec() return v3f(1.0, 0.0, 1.0); } -scene::SMeshBuffer *Minimap::getMinimapMeshBuffer() +irr_ptr Minimap::createMinimapMeshBuffer() { - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + auto buf = make_irr(); auto &vertices = buf->Vertices->Data; auto &indices = buf->Indices->Data; vertices.resize(4); @@ -628,7 +626,7 @@ void Minimap::drawMinimap(core::rect rect) // Draw minimap driver->setTransform(video::ETS_WORLD, matrix); driver->setMaterial(material); - driver->drawMeshBuffer(m_meshbuffer); + driver->drawMeshBuffer(m_meshbuffer.get()); // Draw overlay video::ITexture *minimap_overlay = data->minimap_shape_round ? @@ -636,7 +634,7 @@ void Minimap::drawMinimap(core::rect rect) material.TextureLayers[0].Texture = minimap_overlay; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; driver->setMaterial(material); - driver->drawMeshBuffer(m_meshbuffer); + driver->drawMeshBuffer(m_meshbuffer.get()); // Draw player marker on minimap if (data->minimap_shape_round) { @@ -648,7 +646,7 @@ void Minimap::drawMinimap(core::rect rect) material.TextureLayers[0].Texture = data->player_marker; driver->setTransform(video::ETS_WORLD, matrix); driver->setMaterial(material); - driver->drawMeshBuffer(m_meshbuffer); + driver->drawMeshBuffer(m_meshbuffer.get()); // Reset transformations driver->setTransform(video::ETS_VIEW, oldViewMat); @@ -686,17 +684,17 @@ void Minimap::drawMinimap(core::rect rect) } } -MinimapMarker* Minimap::addMarker(scene::ISceneNode *parent_node) +MinimapMarker *Minimap::addMarker(scene::ISceneNode *parent_node) { - MinimapMarker *m = new MinimapMarker(parent_node); - m_markers.push_back(m); - return m; + auto m = std::make_unique(parent_node); + auto ret = m.get(); + m_markers.push_back(std::move(m)); + return ret; } void Minimap::removeMarker(MinimapMarker **m) { - m_markers.remove(*m); - delete *m; + m_markers.remove_if([ptr = *m](const auto &up) { return up.get() == ptr; }); *m = nullptr; } @@ -710,7 +708,7 @@ void Minimap::updateActiveMarkers() data->mode.scan_height / 2, data->mode.map_size / 2); - for (MinimapMarker *marker : m_markers) { + for (auto &&marker : m_markers) { v3s16 pos = floatToInt(marker->parent_node->getAbsolutePosition() + cam_offset, BS) - pos_offset; if (pos.X < 0 || pos.X > data->mode.map_size || diff --git a/src/client/minimap.h b/src/client/minimap.h index f819deaf4..0c419fa63 100644 --- a/src/client/minimap.h +++ b/src/client/minimap.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "../hud.h" #include "irrlichttypes_extrabloated.h" +#include "irr_ptr.h" #include "util/thread.h" #include "voxel.h" #include @@ -148,7 +149,7 @@ public: void blitMinimapPixelsToImageSurface(video::IImage *map_image, video::IImage *heightmap_image); - scene::SMeshBuffer *getMinimapMeshBuffer(); + irr_ptr createMinimapMeshBuffer(); MinimapMarker* addMarker(scene::ISceneNode *parent_node); void removeMarker(MinimapMarker **marker); @@ -158,20 +159,20 @@ public: video::IVideoDriver *driver; Client* client; - MinimapData *data; + std::unique_ptr data; private: ITextureSource *m_tsrc; IShaderSource *m_shdrsrc; const NodeDefManager *m_ndef; - MinimapUpdateThread *m_minimap_update_thread = nullptr; - scene::SMeshBuffer *m_meshbuffer; + std::unique_ptr m_minimap_update_thread; + irr_ptr m_meshbuffer; bool m_enable_shaders; std::vector m_modes; size_t m_current_mode_index; u16 m_surface_mode_scan_height; f32 m_angle; std::mutex m_mutex; - std::list m_markers; + std::list> m_markers; std::list m_active_markers; }; From 387856a1c329da4e953ed8131fc4de2acfec00fd Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 11 Aug 2024 15:21:45 +0200 Subject: [PATCH 126/200] Load mod profiler in one of the test workflows --- .github/workflows/lua.yml | 2 +- util/test_multiplayer.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lua.yml b/.github/workflows/lua.yml index 0073eca2e..1f83c7688 100644 --- a/.github/workflows/lua.yml +++ b/.github/workflows/lua.yml @@ -35,7 +35,7 @@ jobs: - name: Integration test + devtest run: | - ./util/test_multiplayer.sh + serverconf="profiler.load=true" ./util/test_multiplayer.sh luacheck: name: "Builtin Luacheck and Unit Tests" diff --git a/util/test_multiplayer.sh b/util/test_multiplayer.sh index 624669ac1..b12908423 100755 --- a/util/test_multiplayer.sh +++ b/util/test_multiplayer.sh @@ -33,7 +33,8 @@ printf '%s\n' >"$testspath/client1.conf" \ printf '%s\n' >"$testspath/server.conf" \ max_block_send_distance=1 active_block_range=1 \ - devtest_unittests_autostart=true helper_mode=devtest + devtest_unittests_autostart=true helper_mode=devtest \ + "${serverconf:-}" ln -s "$dir/helper_mod" "$worldpath/worldmods/" From 7ae51382c8a31a6d263ebb913c90025eaf90780c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 11 Aug 2024 13:34:03 +0200 Subject: [PATCH 127/200] Refactor ABM/LBM related code --- src/script/cpp_api/s_env.cpp | 260 ++++++++++++++++++++++++++++------- src/script/cpp_api/s_env.h | 14 ++ src/script/lua_api/l_env.cpp | 87 ------------ src/script/lua_api/l_env.h | 79 +---------- src/serverenvironment.cpp | 92 ++++++------- src/serverenvironment.h | 26 ++-- src/util/basic_macros.h | 6 + 7 files changed, 295 insertions(+), 269 deletions(-) diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 3cbb13cd2..5068e4f4d 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -25,8 +25,102 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen/mapgen.h" #include "lua_api/l_env.h" #include "server.h" +#include "scripting_server.h" #include "script/common/c_content.h" +/* + LuaABM & LuaLBM +*/ + +class LuaABM : public ActiveBlockModifier { +private: + int m_id; + + std::vector m_trigger_contents; + std::vector m_required_neighbors; + float m_trigger_interval; + u32 m_trigger_chance; + bool m_simple_catch_up; + s16 m_min_y; + s16 m_max_y; +public: + LuaABM(int id, + const std::vector &trigger_contents, + const std::vector &required_neighbors, + float trigger_interval, u32 trigger_chance, bool simple_catch_up, + s16 min_y, s16 max_y): + m_id(id), + m_trigger_contents(trigger_contents), + m_required_neighbors(required_neighbors), + m_trigger_interval(trigger_interval), + m_trigger_chance(trigger_chance), + m_simple_catch_up(simple_catch_up), + m_min_y(min_y), + m_max_y(max_y) + { + } + virtual const std::vector &getTriggerContents() const + { + return m_trigger_contents; + } + virtual const std::vector &getRequiredNeighbors() const + { + return m_required_neighbors; + } + virtual float getTriggerInterval() + { + return m_trigger_interval; + } + virtual u32 getTriggerChance() + { + return m_trigger_chance; + } + virtual bool getSimpleCatchUp() + { + return m_simple_catch_up; + } + virtual s16 getMinY() + { + return m_min_y; + } + virtual s16 getMaxY() + { + return m_max_y; + } + + virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, + u32 active_object_count, u32 active_object_count_wider) + { + auto *script = env->getScriptIface(); + script->triggerABM(m_id, p, n, active_object_count, active_object_count_wider); + } +}; + +class LuaLBM : public LoadingBlockModifierDef +{ +private: + int m_id; +public: + LuaLBM(int id, + const std::vector &trigger_contents, + const std::string &name, bool run_at_every_load): + m_id(id) + { + this->run_at_every_load = run_at_every_load; + this->trigger_contents = trigger_contents; + this->name = name; + } + + virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, float dtime_s) + { + auto *script = env->getScriptIface(); + script->triggerLBM(m_id, p, n, dtime_s); + } +}; + +/* + ScriptApiEnv +*/ void ScriptApiEnv::environment_OnGenerated(v3s16 minp, v3s16 maxp, u32 blockseed) @@ -46,7 +140,6 @@ void ScriptApiEnv::environment_OnGenerated(v3s16 minp, v3s16 maxp, void ScriptApiEnv::environment_Step(float dtime) { SCRIPTAPI_PRECHECKHEADER - //infostream << "scriptapi_environment_step" << std::endl; // Get core.registered_globalsteps lua_getglobal(L, "core"); @@ -76,12 +169,40 @@ void ScriptApiEnv::player_event(ServerActiveObject *player, const std::string &t void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) { SCRIPTAPI_PRECHECKHEADER + + assert(env); verbosestream << "ScriptApiEnv: Environment initialized" << std::endl; setEnv(env); - /* - Add {Loading,Active}BlockModifiers to environment - */ + readABMs(); + readLBMs(); +} + +// Reads a single or a list of node names into a vector +bool ScriptApiEnv::read_nodenames(lua_State *L, int idx, std::vector &to) +{ + if (lua_istable(L, idx)) { + const int table = idx < 0 ? (lua_gettop(L) + idx + 1) : idx; + lua_pushnil(L); + while (lua_next(L, table)) { + // key at index -2 and value at index -1 + luaL_checktype(L, -1, LUA_TSTRING); + to.emplace_back(readParam(L, -1)); + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + } else if (lua_isstring(L, idx)) { + to.emplace_back(readParam(L, idx)); + } else { + return false; + } + return true; +} + +void ScriptApiEnv::readABMs() +{ + SCRIPTAPI_PRECHECKHEADER + auto *env = reinterpret_cast(getEnv()); // Get core.registered_abms lua_getglobal(L, "core"); @@ -100,36 +221,12 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) std::vector trigger_contents; lua_getfield(L, current_abm, "nodenames"); - if (lua_istable(L, -1)) { - int table = lua_gettop(L); - lua_pushnil(L); - while (lua_next(L, table)) { - // key at index -2 and value at index -1 - luaL_checktype(L, -1, LUA_TSTRING); - trigger_contents.emplace_back(readParam(L, -1)); - // removes value, keeps key for next iteration - lua_pop(L, 1); - } - } else if (lua_isstring(L, -1)) { - trigger_contents.emplace_back(readParam(L, -1)); - } + read_nodenames(L, -1, trigger_contents); lua_pop(L, 1); std::vector required_neighbors; lua_getfield(L, current_abm, "neighbors"); - if (lua_istable(L, -1)) { - int table = lua_gettop(L); - lua_pushnil(L); - while (lua_next(L, table)) { - // key at index -2 and value at index -1 - luaL_checktype(L, -1, LUA_TSTRING); - required_neighbors.emplace_back(readParam(L, -1)); - // removes value, keeps key for next iteration - lua_pop(L, 1); - } - } else if (lua_isstring(L, -1)) { - required_neighbors.emplace_back(readParam(L, -1)); - } + read_nodenames(L, -1, required_neighbors); lua_pop(L, 1); float trigger_interval = 10.0; @@ -151,7 +248,7 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) luaL_checktype(L, current_abm + 1, LUA_TFUNCTION); lua_pop(L, 1); - LuaABM *abm = new LuaABM(L, id, trigger_contents, required_neighbors, + LuaABM *abm = new LuaABM(id, trigger_contents, required_neighbors, trigger_interval, trigger_chance, simple_catch_up, min_y, max_y); env->addActiveBlockModifier(abm); @@ -160,6 +257,12 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) lua_pop(L, 1); } lua_pop(L, 1); +} + +void ScriptApiEnv::readLBMs() +{ + SCRIPTAPI_PRECHECKHEADER + auto *env = reinterpret_cast(getEnv()); // Get core.registered_lbms lua_getglobal(L, "core"); @@ -177,21 +280,9 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) int id = lua_tonumber(L, -2); int current_lbm = lua_gettop(L); - std::set trigger_contents; + std::vector trigger_contents; lua_getfield(L, current_lbm, "nodenames"); - if (lua_istable(L, -1)) { - int table = lua_gettop(L); - lua_pushnil(L); - while (lua_next(L, table)) { - // key at index -2 and value at index -1 - luaL_checktype(L, -1, LUA_TSTRING); - trigger_contents.insert(readParam(L, -1)); - // removes value, keeps key for next iteration - lua_pop(L, 1); - } - } else if (lua_isstring(L, -1)) { - trigger_contents.insert(readParam(L, -1)); - } + read_nodenames(L, -1, trigger_contents); lua_pop(L, 1); std::string name; @@ -204,7 +295,7 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) luaL_checktype(L, current_lbm + 1, LUA_TFUNCTION); lua_pop(L, 1); - LuaLBM *lbm = new LuaLBM(L, id, trigger_contents, name, + LuaLBM *lbm = new LuaLBM(id, trigger_contents, name, run_at_every_load); env->addLoadingBlockModifierDef(lbm); @@ -288,7 +379,7 @@ void ScriptApiEnv::on_liquid_transformed( int index = 1; lua_createtable(L, list.size(), 0); lua_createtable(L, list.size(), 0); - for(std::pair p : list) { + for(auto &p : list) { lua_pushnumber(L, index); push_v3s16(L, p.first); lua_rawset(L, -4); @@ -332,3 +423,78 @@ bool ScriptApiEnv::has_on_mapblocks_changed() luaL_checktype(L, -1, LUA_TTABLE); return lua_objlen(L, -1) > 0; } + +void ScriptApiEnv::triggerABM(int id, v3s16 p, MapNode n, + u32 active_object_count, u32 active_object_count_wider) +{ + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + + // Get registered_abms + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_abms"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_remove(L, -2); // Remove core + + // Get registered_abms[m_id] + lua_pushinteger(L, id); + lua_gettable(L, -2); + FATAL_ERROR_IF(lua_isnil(L, -1), "Entry with given id not found in registered_abms table"); + lua_remove(L, -2); // Remove registered_abms + + setOriginFromTable(-1); + + // Call action + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "action"); + luaL_checktype(L, -1, LUA_TFUNCTION); + lua_remove(L, -2); // Remove registered_abms[m_id] + push_v3s16(L, p); + pushnode(L, n); + lua_pushnumber(L, active_object_count); + lua_pushnumber(L, active_object_count_wider); + + int result = lua_pcall(L, 4, 0, error_handler); + if (result) + scriptError(result, "LuaABM::trigger"); + + lua_pop(L, 1); // Pop error handler +} + +void ScriptApiEnv::triggerLBM(int id, v3s16 p, + const MapNode n, const float dtime_s) +{ + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + + // Get registered_lbms + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_lbms"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_remove(L, -2); // Remove core + + // Get registered_lbms[m_id] + lua_pushinteger(L, id); + lua_gettable(L, -2); + FATAL_ERROR_IF(lua_isnil(L, -1), "Entry with given id not found in registered_lbms table"); + lua_remove(L, -2); // Remove registered_lbms + + setOriginFromTable(-1); + + // Call action + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "action"); + luaL_checktype(L, -1, LUA_TFUNCTION); + lua_remove(L, -2); // Remove registered_lbms[m_id] + push_v3s16(L, p); + pushnode(L, n); + lua_pushnumber(L, dtime_s); + + int result = lua_pcall(L, 3, 0, error_handler); + if (result) + scriptError(result, "LuaLBM::trigger"); + + lua_pop(L, 1); // Pop error handler +} diff --git a/src/script/cpp_api/s_env.h b/src/script/cpp_api/s_env.h index bc4c4cd4d..8a88749fa 100644 --- a/src/script/cpp_api/s_env.h +++ b/src/script/cpp_api/s_env.h @@ -55,5 +55,19 @@ public: // Determines whether there are any on_mapblocks_changed callbacks bool has_on_mapblocks_changed(); + // Initializes environment and loads some definitions from Lua void initializeEnvironment(ServerEnvironment *env); + + void triggerABM(int id, v3s16 p, MapNode n, + u32 active_object_count, u32 active_object_count_wider); + + void triggerLBM(int id, v3s16 p, MapNode n, float dtime_s); + +private: + void readABMs(); + + void readLBMs(); + + // Reads a single or a list of node names into a vector + static bool read_nodenames(lua_State *L, int idx, std::vector &to); }; diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index f5ed2804c..6fd0e2b2a 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -65,93 +65,6 @@ const EnumString ModApiEnvBase::es_BlockStatusType[] = /////////////////////////////////////////////////////////////////////////////// - -void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, - u32 active_object_count, u32 active_object_count_wider) -{ - ServerScripting *scriptIface = env->getScriptIface(); - scriptIface->realityCheck(); - - lua_State *L = scriptIface->getStack(); - sanity_check(lua_checkstack(L, 20)); - StackUnroller stack_unroller(L); - - int error_handler = PUSH_ERROR_HANDLER(L); - - // Get registered_abms - lua_getglobal(L, "core"); - lua_getfield(L, -1, "registered_abms"); - luaL_checktype(L, -1, LUA_TTABLE); - lua_remove(L, -2); // Remove core - - // Get registered_abms[m_id] - lua_pushinteger(L, m_id); - lua_gettable(L, -2); - if(lua_isnil(L, -1)) - FATAL_ERROR(""); - lua_remove(L, -2); // Remove registered_abms - - scriptIface->setOriginFromTable(-1); - - // Call action - luaL_checktype(L, -1, LUA_TTABLE); - lua_getfield(L, -1, "action"); - luaL_checktype(L, -1, LUA_TFUNCTION); - lua_remove(L, -2); // Remove registered_abms[m_id] - push_v3s16(L, p); - pushnode(L, n); - lua_pushnumber(L, active_object_count); - lua_pushnumber(L, active_object_count_wider); - - int result = lua_pcall(L, 4, 0, error_handler); - if (result) - scriptIface->scriptError(result, "LuaABM::trigger"); - - lua_pop(L, 1); // Pop error handler -} - -void LuaLBM::trigger(ServerEnvironment *env, v3s16 p, - const MapNode n, const float dtime_s) -{ - ServerScripting *scriptIface = env->getScriptIface(); - scriptIface->realityCheck(); - - lua_State *L = scriptIface->getStack(); - sanity_check(lua_checkstack(L, 20)); - StackUnroller stack_unroller(L); - - int error_handler = PUSH_ERROR_HANDLER(L); - - // Get registered_lbms - lua_getglobal(L, "core"); - lua_getfield(L, -1, "registered_lbms"); - luaL_checktype(L, -1, LUA_TTABLE); - lua_remove(L, -2); // Remove core - - // Get registered_lbms[m_id] - lua_pushinteger(L, m_id); - lua_gettable(L, -2); - FATAL_ERROR_IF(lua_isnil(L, -1), "Entry with given id not found in registered_lbms table"); - lua_remove(L, -2); // Remove registered_lbms - - scriptIface->setOriginFromTable(-1); - - // Call action - luaL_checktype(L, -1, LUA_TTABLE); - lua_getfield(L, -1, "action"); - luaL_checktype(L, -1, LUA_TFUNCTION); - lua_remove(L, -2); // Remove registered_lbms[m_id] - push_v3s16(L, p); - pushnode(L, n); - lua_pushnumber(L, dtime_s); - - int result = lua_pcall(L, 3, 0, error_handler); - if (result) - scriptIface->scriptError(result, "LuaLBM::trigger"); - - lua_pop(L, 1); // Pop error handler -} - int LuaRaycast::l_next(lua_State *L) { GET_PLAIN_ENV_PTR; diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 2ed0eb114..b92a6f935 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -20,9 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "lua_api/l_base.h" -#include "serverenvironment.h" #include "raycast.h" +class ServerScripting; + // base class containing helpers class ModApiEnvBase : public ModApiBase { protected: @@ -281,82 +282,6 @@ public: static void InitializeEmerge(lua_State *L, int top); }; -class LuaABM : public ActiveBlockModifier { -private: - int m_id; - - std::vector m_trigger_contents; - std::vector m_required_neighbors; - float m_trigger_interval; - u32 m_trigger_chance; - bool m_simple_catch_up; - s16 m_min_y; - s16 m_max_y; -public: - LuaABM(lua_State *L, int id, - const std::vector &trigger_contents, - const std::vector &required_neighbors, - float trigger_interval, u32 trigger_chance, bool simple_catch_up, s16 min_y, s16 max_y): - m_id(id), - m_trigger_contents(trigger_contents), - m_required_neighbors(required_neighbors), - m_trigger_interval(trigger_interval), - m_trigger_chance(trigger_chance), - m_simple_catch_up(simple_catch_up), - m_min_y(min_y), - m_max_y(max_y) - { - } - virtual const std::vector &getTriggerContents() const - { - return m_trigger_contents; - } - virtual const std::vector &getRequiredNeighbors() const - { - return m_required_neighbors; - } - virtual float getTriggerInterval() - { - return m_trigger_interval; - } - virtual u32 getTriggerChance() - { - return m_trigger_chance; - } - virtual bool getSimpleCatchUp() - { - return m_simple_catch_up; - } - virtual s16 getMinY() - { - return m_min_y; - } - virtual s16 getMaxY() - { - return m_max_y; - } - virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, - u32 active_object_count, u32 active_object_count_wider); -}; - -class LuaLBM : public LoadingBlockModifierDef -{ -private: - int m_id; -public: - LuaLBM(lua_State *L, int id, - const std::set &trigger_contents, - const std::string &name, - bool run_at_every_load): - m_id(id) - { - this->run_at_every_load = run_at_every_load; - this->trigger_contents = trigger_contents; - this->name = name; - } - virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, float dtime_s); -}; - //! Lua wrapper for RaycastState objects class LuaRaycast : public ModApiBase { diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 24e4a587e..f79102e74 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -77,11 +77,11 @@ ABMWithState::ABMWithState(ActiveBlockModifier *abm_): LBMManager */ -void LBMContentMapping::deleteContents() +LBMContentMapping::~LBMContentMapping() { - for (auto &it : lbm_list) { + map.clear(); + for (auto &it : lbm_list) delete it; - } } void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef) @@ -90,29 +90,32 @@ void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamed // Unknown names get added to the global NameIdMapping. const NodeDefManager *nodedef = gamedef->ndef(); + FATAL_ERROR_IF(CONTAINS(lbm_list, lbm_def), "Same LBM registered twice"); lbm_list.push_back(lbm_def); - for (const std::string &nodeTrigger: lbm_def->trigger_contents) { - std::vector c_ids; - bool found = nodedef->getIds(nodeTrigger, c_ids); + std::vector c_ids; + + for (const auto &node : lbm_def->trigger_contents) { + bool found = nodedef->getIds(node, c_ids); if (!found) { - content_t c_id = gamedef->allocateUnknownNodeId(nodeTrigger); + content_t c_id = gamedef->allocateUnknownNodeId(node); if (c_id == CONTENT_IGNORE) { // Seems it can't be allocated. - warningstream << "Could not internalize node name \"" << nodeTrigger + warningstream << "Could not internalize node name \"" << node << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl; continue; } c_ids.push_back(c_id); } - - for (content_t c_id : c_ids) { - map[c_id].push_back(lbm_def); - } } + + SORT_AND_UNIQUE(c_ids); + + for (content_t c_id : c_ids) + map[c_id].push_back(lbm_def); } -const std::vector * +const LBMContentMapping::lbm_vector * LBMContentMapping::lookup(content_t c) const { lbm_map::const_iterator it = map.find(c); @@ -130,9 +133,7 @@ LBMManager::~LBMManager() delete m_lbm_def.second; } - for (auto &it : m_lbm_lookup) { - (it.second).deleteContents(); - } + m_lbm_lookup.clear(); } void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def) @@ -236,7 +237,7 @@ std::string LBMManager::createIntroductionTimesString() std::ostringstream oss; for (const auto &it : m_lbm_lookup) { u32 time = it.first; - const std::vector &lbm_list = it.second.lbm_list; + auto &lbm_list = it.second.getList(); for (const auto &lbm_def : lbm_list) { // Don't add if the LBM runs at every load, // then introducement time is hardcoded @@ -255,16 +256,17 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, // Precondition, we need m_lbm_lookup to be initialized FATAL_ERROR_IF(!m_query_mode, "attempted to query on non fully set up LBMManager"); - v3s16 pos_of_block = block->getPosRelative(); + + const v3s16 pos_of_block = block->getPosRelative(); v3s16 pos; MapNode n; content_t c; auto it = getLBMsIntroducedAfter(stamp); + // Note: the iteration count of this outer loop is typically very low, so it's ok. for (; it != m_lbm_lookup.end(); ++it) { - // Cache previous version to speedup lookup which has a very high performance - // penalty on each call + // Cache previous lookup result since it has a high performance penalty. content_t previous_c = CONTENT_IGNORE; - const std::vector *lbm_list = nullptr; + const LBMContentMapping::lbm_vector *lbm_list = nullptr; for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) @@ -272,7 +274,6 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, n = block->getNodeNoCheck(pos); c = n.getContent(); - // If content_t are not matching perform an LBM lookup if (previous_c != c) { lbm_list = it->second.lookup(c); previous_c = c; @@ -792,11 +793,9 @@ void ServerEnvironment::loadDefaultMeta() struct ActiveABM { ActiveBlockModifier *abm; - int chance; std::vector required_neighbors; - bool check_required_neighbors; // false if required_neighbors is known to be empty - s16 min_y; - s16 max_y; + int chance; + s16 min_y, max_y; }; #define CONTENT_TYPE_CACHE_MAX 64 @@ -812,16 +811,16 @@ public: bool use_timers): m_env(env) { - if(dtime_s < 0.001) + if (dtime_s < 0.001f) return; const NodeDefManager *ndef = env->getGameDef()->ndef(); for (ABMWithState &abmws : abms) { ActiveBlockModifier *abm = abmws.abm; float trigger_interval = abm->getTriggerInterval(); - if(trigger_interval < 0.001) - trigger_interval = 0.001; + if (trigger_interval < 0.001f) + trigger_interval = 0.001f; float actual_interval = dtime_s; - if(use_timers){ + if (use_timers) { abmws.timer += dtime_s; if(abmws.timer < trigger_interval) continue; @@ -831,6 +830,7 @@ public: float chance = abm->getTriggerChance(); if (chance == 0) chance = 1; + ActiveABM aabm; aabm.abm = abm; if (abm->getSimpleCatchUp()) { @@ -848,25 +848,21 @@ public: aabm.max_y = abm->getMaxY(); // Trigger neighbors - const std::vector &required_neighbors_s = - abm->getRequiredNeighbors(); - for (const std::string &required_neighbor_s : required_neighbors_s) { - ndef->getIds(required_neighbor_s, aabm.required_neighbors); - } - aabm.check_required_neighbors = !required_neighbors_s.empty(); + for (const auto &s : abm->getRequiredNeighbors()) + ndef->getIds(s, aabm.required_neighbors); + SORT_AND_UNIQUE(aabm.required_neighbors); // Trigger contents - const std::vector &contents_s = abm->getTriggerContents(); - for (const std::string &content_s : contents_s) { - std::vector ids; - ndef->getIds(content_s, ids); - for (content_t c : ids) { - if (c >= m_aabms.size()) - m_aabms.resize(c + 256, NULL); - if (!m_aabms[c]) - m_aabms[c] = new std::vector; - m_aabms[c]->push_back(aabm); - } + std::vector ids; + for (const auto &s : abm->getTriggerContents()) + ndef->getIds(s, ids); + SORT_AND_UNIQUE(ids); + for (content_t c : ids) { + if (c >= m_aabms.size()) + m_aabms.resize(c + 256, nullptr); + if (!m_aabms[c]) + m_aabms[c] = new std::vector; + m_aabms[c]->push_back(aabm); } } } @@ -967,7 +963,7 @@ public: continue; // Check neighbors - if (aabm.check_required_neighbors) { + if (!aabm.required_neighbors.empty()) { v3s16 p1; for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++) for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++) diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 7a388d21c..51d699cab 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -90,7 +90,7 @@ struct ABMWithState struct LoadingBlockModifierDef { // Set of contents to trigger on - std::set trigger_contents; + std::vector trigger_contents; std::string name; bool run_at_every_load = false; @@ -100,19 +100,25 @@ struct LoadingBlockModifierDef MapNode n, float dtime_s) {}; }; -struct LBMContentMapping +class LBMContentMapping { - typedef std::unordered_map> lbm_map; - lbm_map map; +public: + typedef std::vector lbm_vector; + typedef std::unordered_map lbm_map; - std::vector lbm_list; - - // Needs to be separate method (not inside destructor), - // because the LBMContentMapping may be copied and destructed - // many times during operation in the lbm_lookup_map. - void deleteContents(); + LBMContentMapping() = default; void addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef); const lbm_map::mapped_type *lookup(content_t c) const; + const lbm_vector &getList() const { return lbm_list; } + + // This struct owns the LBM pointers. + ~LBMContentMapping(); + DISABLE_CLASS_COPY(LBMContentMapping); + ALLOW_CLASS_MOVE(LBMContentMapping); + +private: + lbm_vector lbm_list; + lbm_map map; }; class LBMManager diff --git a/src/util/basic_macros.h b/src/util/basic_macros.h index 7c3746417..f0fd4eb48 100644 --- a/src/util/basic_macros.h +++ b/src/util/basic_macros.h @@ -28,6 +28,12 @@ with this program; if not, write to the Free Software Foundation, Inc., // Requires #define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end()) +// Requires +#define SORT_AND_UNIQUE(c) do { \ + std::sort((c).begin(), (c).end()); \ + (c).erase(std::unique((c).begin(), (c).end()), (c).end()); \ + } while (0) + // To disable copy constructors and assignment operations for some class // 'Foobar', add the macro DISABLE_CLASS_COPY(Foobar) in the class definition. // Note this also disables copying for any classes derived from 'Foobar' as well From 811adf5d42844bbd40e98e07773db3d16e104b90 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 11 Aug 2024 14:54:02 +0200 Subject: [PATCH 128/200] Bulk LBMs (#14954) --- builtin/game/features.lua | 1 + builtin/game/misc.lua | 25 +++++++++++ builtin/game/register.lua | 7 ++- builtin/profiler/instrumentation.lua | 5 ++- doc/lua_api.md | 17 +++++++- src/script/cpp_api/s_env.cpp | 42 ++++++++---------- src/script/cpp_api/s_env.h | 4 +- src/serverenvironment.cpp | 65 +++++++++++++++++++++------- src/serverenvironment.h | 9 +++- 9 files changed, 126 insertions(+), 49 deletions(-) diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 358ee8ff0..81b291e6c 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -43,6 +43,7 @@ core.features = { moveresult_new_pos = true, override_item_remove_fields = true, hotbar_hud_element = true, + bulk_lbms = true, } function core.has_feature(arg) diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 91ca738a4..25e19ddb0 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -298,3 +298,28 @@ do return valid_object_iterator(core.get_objects_in_area(min_pos, max_pos)) end end + +-- +-- Helper for LBM execution, called from C++ +-- + +function core.run_lbm(id, pos_list, dtime_s) + local lbm = core.registered_lbms[id] + assert(lbm, "Entry with given id not found in registered_lbms table") + core.set_last_run_mod(lbm.mod_origin) + if lbm.bulk_action then + return lbm.bulk_action(pos_list, dtime_s) + end + -- emulate non-bulk LBMs + local expect = core.get_node(pos_list[1]).name + -- engine guarantees that + -- 1) all nodes are the same content type + -- 2) the list is up-to-date when we're called + assert(expect ~= "ignore") + for _, pos in ipairs(pos_list) do + local n = core.get_node(pos) + if n.name == expect then -- might have been changed by previous call + lbm.action(pos, n, dtime_s) + end + end +end diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 76cb72f87..3d99a6925 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -105,7 +105,12 @@ function core.register_lbm(spec) -- Add to core.registered_lbms check_modname_prefix(spec.name) check_node_list(spec.nodenames, "nodenames") - assert(type(spec.action) == "function", "Required field 'action' of type function") + local have = spec.action ~= nil + local have_bulk = spec.bulk_action ~= nil + assert(not have or type(spec.action) == "function", "Field 'action' must be a function") + assert(not have_bulk or type(spec.bulk_action) == "function", "Field 'bulk_action' must be a function") + assert(have ~= have_bulk, "Either 'action' or 'bulk_action' must be present") + core.registered_lbms[#core.registered_lbms + 1] = spec spec.mod_origin = core.get_current_modname() or "??" end diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua index 0ffd9e6a9..c4feda7b4 100644 --- a/builtin/profiler/instrumentation.lua +++ b/builtin/profiler/instrumentation.lua @@ -217,8 +217,9 @@ local function init() -- Wrap register_lbm() to automatically instrument lbms. local orig_register_lbm = core.register_lbm core.register_lbm = function(spec) - spec.action = instrument { - func = spec.action, + local k = spec.bulk_action ~= nil and "bulk_action" or "action" + spec[k] = instrument { + func = spec[k], class = "LBM", label = spec.label or spec.name, } diff --git a/doc/lua_api.md b/doc/lua_api.md index 83f186804..b06809aee 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5522,6 +5522,8 @@ Utilities override_item_remove_fields = true, -- The predefined hotbar is a Lua HUD element of type `hotbar` (5.10.0) hotbar_hud_element = true, + -- Bulk LBM support (5.10.0) + bulk_lbms = true, } ``` @@ -9124,7 +9126,12 @@ Used by `minetest.register_lbm`. A loading block modifier (LBM) is used to define a function that is called for specific nodes (defined by `nodenames`) when a mapblock which contains such nodes -gets activated (not loaded!) +gets activated (not loaded!). + +Note: LBMs operate on a "snapshot" of node positions taken once before they are triggered. +That means if an LBM callback adds a node, it won't be taken into account. +However the engine guarantees that when the callback is called that all given position(s) +contain a matching node. ```lua { @@ -9148,7 +9155,13 @@ gets activated (not loaded!) action = function(pos, node, dtime_s) end, -- Function triggered for each qualifying node. -- `dtime_s` is the in-game time (in seconds) elapsed since the block - -- was last active + -- was last active. + + bulk_action = function(pos_list, dtime_s) end, + -- Function triggered with a list of all applicable node positions at once. + -- This can be provided as an alternative to `action` (not both). + -- Available since `minetest.features.bulk_lbms` (5.10.0) + -- `dtime_s`: as above } ``` diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 5068e4f4d..deac90f3c 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -111,10 +111,11 @@ public: this->name = name; } - virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, float dtime_s) + virtual void trigger(ServerEnvironment *env, MapBlock *block, + const std::unordered_set &positions, float dtime_s) { auto *script = env->getScriptIface(); - script->triggerLBM(m_id, p, n, dtime_s); + script->triggerLBM(m_id, block, positions, dtime_s); } }; @@ -291,10 +292,6 @@ void ScriptApiEnv::readLBMs() bool run_at_every_load = getboolfield_default(L, current_lbm, "run_at_every_load", false); - lua_getfield(L, current_lbm, "action"); - luaL_checktype(L, current_lbm + 1, LUA_TFUNCTION); - lua_pop(L, 1); - LuaLBM *lbm = new LuaLBM(id, trigger_contents, name, run_at_every_load); @@ -462,34 +459,29 @@ void ScriptApiEnv::triggerABM(int id, v3s16 p, MapNode n, lua_pop(L, 1); // Pop error handler } -void ScriptApiEnv::triggerLBM(int id, v3s16 p, - const MapNode n, const float dtime_s) +void ScriptApiEnv::triggerLBM(int id, MapBlock *block, + const std::unordered_set &positions, float dtime_s) { SCRIPTAPI_PRECHECKHEADER int error_handler = PUSH_ERROR_HANDLER(L); - // Get registered_lbms + const v3s16 pos_of_block = block->getPosRelative(); + + // Get core.run_lbm lua_getglobal(L, "core"); - lua_getfield(L, -1, "registered_lbms"); - luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "run_lbm"); + luaL_checktype(L, -1, LUA_TFUNCTION); lua_remove(L, -2); // Remove core - // Get registered_lbms[m_id] + // Call it lua_pushinteger(L, id); - lua_gettable(L, -2); - FATAL_ERROR_IF(lua_isnil(L, -1), "Entry with given id not found in registered_lbms table"); - lua_remove(L, -2); // Remove registered_lbms - - setOriginFromTable(-1); - - // Call action - luaL_checktype(L, -1, LUA_TTABLE); - lua_getfield(L, -1, "action"); - luaL_checktype(L, -1, LUA_TFUNCTION); - lua_remove(L, -2); // Remove registered_lbms[m_id] - push_v3s16(L, p); - pushnode(L, n); + lua_createtable(L, positions.size(), 0); + int i = 1; + for (auto &p : positions) { + push_v3s16(L, pos_of_block + p); + lua_rawseti(L, -2, i++); + } lua_pushnumber(L, dtime_s); int result = lua_pcall(L, 3, 0, error_handler); diff --git a/src/script/cpp_api/s_env.h b/src/script/cpp_api/s_env.h index 8a88749fa..4722cb522 100644 --- a/src/script/cpp_api/s_env.h +++ b/src/script/cpp_api/s_env.h @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include class ServerEnvironment; +class MapBlock; struct ScriptCallbackState; class ScriptApiEnv : virtual public ScriptApiBase @@ -61,7 +62,8 @@ public: void triggerABM(int id, v3s16 p, MapNode n, u32 active_object_count, u32 active_object_count_wider); - void triggerLBM(int id, v3s16 p, MapNode n, float dtime_s); + void triggerLBM(int id, MapBlock *block, + const std::unordered_set &positions, float dtime_s); private: void readABMs(); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index f79102e74..ac627dd50 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -257,40 +257,73 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, FATAL_ERROR_IF(!m_query_mode, "attempted to query on non fully set up LBMManager"); - const v3s16 pos_of_block = block->getPosRelative(); - v3s16 pos; - MapNode n; - content_t c; - auto it = getLBMsIntroducedAfter(stamp); + // Collect a list of all LBMs and associated positions + struct LBMToRun { + std::unordered_set p; // node positions + std::unordered_set l; + }; + std::unordered_map to_run; + // Note: the iteration count of this outer loop is typically very low, so it's ok. - for (; it != m_lbm_lookup.end(); ++it) { - // Cache previous lookup result since it has a high performance penalty. + for (auto it = getLBMsIntroducedAfter(stamp); it != m_lbm_lookup.end(); ++it) { + v3s16 pos; + content_t c; + + // Cache previous lookups since it has a high performance penalty. content_t previous_c = CONTENT_IGNORE; const LBMContentMapping::lbm_vector *lbm_list = nullptr; + LBMToRun *batch = nullptr; for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) { - n = block->getNodeNoCheck(pos); - c = n.getContent(); + c = block->getNodeNoCheck(pos).getContent(); + bool c_changed = false; if (previous_c != c) { + c_changed = true; lbm_list = it->second.lookup(c); + batch = &to_run[c]; previous_c = c; } if (!lbm_list) continue; - for (auto lbmdef : *lbm_list) { - lbmdef->trigger(env, pos + pos_of_block, n, dtime_s); - if (block->isOrphan()) - return; - n = block->getNodeNoCheck(pos); - if (n.getContent() != c) - break; // The node was changed and the LBMs no longer apply + batch->p.insert(pos); + if (c_changed) { + batch->l.insert(lbm_list->begin(), lbm_list->end()); + } else { + // we were here before so the list must be filled + assert(!batch->l.empty()); } } } + + // Actually run them + bool first = true; + for (auto &[c, batch] : to_run) { + for (auto &lbm_def : batch.l) { + if (!first) { + // The fun part: since any LBM call can change the nodes inside of he + // block, we have to recheck the positions to see if the wanted node + // is still there. + // Note that we don't rescan the whole block, we don't want to include new changes. + for (auto it2 = batch.p.begin(); it2 != batch.p.end(); ) { + if (block->getNodeNoCheck(*it2).getContent() != c) + it2 = batch.p.erase(it2); + else + ++it2; + } + } + first = false; + + if (batch.p.empty()) + break; + lbm_def->trigger(env, block, batch.p, dtime_s); + if (block->isOrphan()) + return; + } + } } /* diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 51d699cab..d20cc0b3f 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -96,8 +96,13 @@ struct LoadingBlockModifierDef virtual ~LoadingBlockModifierDef() = default; - virtual void trigger(ServerEnvironment *env, v3s16 p, - MapNode n, float dtime_s) {}; + /// @brief Called to invoke LBM + /// @param env environment + /// @param block the block in question + /// @param positions set of node positions (block-relative!) + /// @param dtime_s game time since last deactivation + virtual void trigger(ServerEnvironment *env, MapBlock *block, + const std::unordered_set &positions, float dtime_s) {}; }; class LBMContentMapping From 4ac86db8e3300492aee0d0444e638f188a430603 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Fri, 20 Sep 2024 15:05:51 +0200 Subject: [PATCH 129/200] Simplify getGameTime function usage (#15187) --- src/script/lua_api/l_env.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 6fd0e2b2a..a024d7e42 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -749,10 +749,7 @@ int ModApiEnv::l_get_timeofday(lua_State *L) { GET_PLAIN_ENV_PTR; - // Do it - int timeofday_mh = env->getTimeOfDay(); - float timeofday_f = (float)timeofday_mh / 24000.0f; - lua_pushnumber(L, timeofday_f); + lua_pushnumber(L, env->getTimeOfDayF()); return 1; } @@ -770,8 +767,7 @@ int ModApiEnv::l_get_gametime(lua_State *L) { GET_ENV_PTR; - int game_time = env->getGameTime(); - lua_pushnumber(L, game_time); + lua_pushnumber(L, env->getGameTime()); return 1; } From d8f1daac2546c66d28d2e36a28c013ce56bddb4e Mon Sep 17 00:00:00 2001 From: GefullteTaubenbrust2 <72752000+GefullteTaubenbrust2@users.noreply.github.com> Date: Tue, 24 Sep 2024 20:14:27 +0200 Subject: [PATCH 130/200] Visual Effects Vol. 1 (#14610) This PR adds a variety of effects to enhance the visual experience. "soft" clouds look Tinted shadows Crude water reflections (sky and sun) and waves Translucent foliage Node specular highlights Adjusted fog color (more saturated where the fog is lighter) Minor changes to volumetric lighting (crudely simulates the effect of depth) Co-authored-by: sfan5 --- builtin/settingtypes.txt | 22 +++ .../shaders/nodes_shader/opengl_fragment.glsl | 135 +++++++++++++++++- .../shaders/nodes_shader/opengl_vertex.glsl | 2 + .../object_shader/opengl_fragment.glsl | 11 +- .../volumetric_light/opengl_fragment.glsl | 4 +- doc/lua_api.md | 5 + src/client/clientevent.h | 1 + src/client/clientmap.cpp | 18 ++- src/client/clouds.cpp | 117 ++++++++++----- src/client/clouds.h | 8 ++ src/client/content_mapblock.cpp | 22 ++- src/client/content_mapblock.h | 1 + src/client/game.cpp | 11 +- src/client/shader.cpp | 9 ++ src/client/shadows/dynamicshadowsrender.h | 3 + src/client/shadows/shadowsshadercallbacks.cpp | 3 + src/client/shadows/shadowsshadercallbacks.h | 1 + src/defaultsettings.cpp | 4 + src/lighting.h | 3 + src/network/clientpackethandler.cpp | 8 ++ src/network/networkprotocol.h | 5 +- src/script/lua_api/l_object.cpp | 10 ++ src/server.cpp | 4 +- src/skyparams.h | 2 + 24 files changed, 356 insertions(+), 53 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 54a639a89..d13f25695 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -403,6 +403,11 @@ enable_clouds (Clouds) bool true # Requires: enable_clouds enable_3d_clouds (3D clouds) bool true +# Use smooth cloud shading. +# +# Requires: enable_3d_clouds, enable_clouds +soft_clouds (Soft clouds) bool false + [**Filtering and Antialiasing] # Use mipmaps when scaling textures. May slightly increase performance, @@ -505,6 +510,11 @@ water_wave_length (Waving liquids wavelength) float 20.0 0.1 # Requires: shaders, enable_waving_water water_wave_speed (Waving liquids wave speed) float 5.0 +# When enabled, liquid reflections are simulated. +# +# Requires: shaders, enable_waving_water, enable_dynamic_shadows +enable_water_reflections (Liquid reflections) bool false + [**Dynamic shadows] # Set to true to enable Shadow Mapping. @@ -661,6 +671,18 @@ bloom_radius (Bloom Radius) float 1 0.1 8 # Requires: shaders, enable_post_processing, enable_bloom enable_volumetric_lighting (Volumetric lighting) bool false +[**Other Effects] + +# Simulate translucency when looking at foliage in the sunlight. +# +# Requires: shaders, enable_dynamic_shadows +enable_translucent_foliage (Translucent foliage) bool false + +# Apply specular shading to nodes. +# +# Requires: shaders, enable_dynamic_shadows +enable_node_specular (Node specular) bool false + [*Audio] # Volume of all sounds. diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl index 46977b147..c560a8505 100644 --- a/client/shaders/nodes_shader/opengl_fragment.glsl +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -1,3 +1,7 @@ +#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT) +#define MATERIAL_WAVING_LIQUID 1 +#endif + uniform sampler2D baseTexture; uniform vec3 dayLight; @@ -7,6 +11,7 @@ uniform float fogShadingParameter; // The cameraOffset is the current center of the visible world. uniform highp vec3 cameraOffset; +uniform vec3 cameraPosition; uniform float animationTimer; #ifdef ENABLE_DYNAMIC_SHADOWS // shadow texture @@ -20,6 +25,7 @@ uniform float animationTimer; uniform vec4 CameraPos; uniform float xyPerspectiveBias0; uniform float xyPerspectiveBias1; + uniform vec3 shadow_tint; varying float adj_shadow_strength; varying float cosLight; @@ -47,6 +53,49 @@ varying highp vec3 eyeVec; varying float nightRatio; #ifdef ENABLE_DYNAMIC_SHADOWS +#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS) && ENABLE_WAVING_WATER) +vec4 perm(vec4 x) +{ + return mod(((x * 34.0) + 1.0) * x, 289.0); +} + +// Corresponding gradient of snoise +vec3 gnoise(vec3 p){ + vec3 a = floor(p); + vec3 d = p - a; + vec3 dd = 6.0 * d * (1.0 - d); + d = d * d * (3.0 - 2.0 * d); + + vec4 b = a.xxyy + vec4(0.0, 1.0, 0.0, 1.0); + vec4 k1 = perm(b.xyxy); + vec4 k2 = perm(k1.xyxy + b.zzww); + + vec4 c = k2 + a.zzzz; + vec4 k3 = perm(c); + vec4 k4 = perm(c + 1.0); + + vec4 o1 = fract(k3 * (1.0 / 41.0)); + vec4 o2 = fract(k4 * (1.0 / 41.0)); + + vec4 o3 = o2 * d.z + o1 * (1.0 - d.z); + vec2 o4 = o3.yw * d.x + o3.xz * (1.0 - d.x); + + vec4 dz1 = (o2 - o1) * dd.z; + vec2 dz2 = dz1.yw * d.x + dz1.xz * (1.0 - d.x); + + vec2 dx = (o3.yw - o3.xz) * dd.x; + + return vec3( + dx.y * d.y + dx.x * (1. - d.y), + (o4.y - o4.x) * dd.y, + dz2.y * d.y + dz2.x * (1. - d.y) + ); +} + +vec2 wave_noise(vec3 p, float off) { + return (gnoise(p + vec3(0.0, 0.0, off)) * 0.4 + gnoise(2.0 * p + vec3(0.0, off, off)) * 0.2 + gnoise(3.0 * p + vec3(0.0, off, off)) * 0.225 + gnoise(4.0 * p + vec3(-off, off, 0.0)) * 0.2).xz; +} +#endif // assuming near is always 1.0 float getLinearDepth() @@ -66,6 +115,14 @@ float mtsmoothstep(in float edge0, in float edge1, in float x) return t * t * (3.0 - 2.0 * t); } +float shadowCutoff(float x) { + #if defined(ENABLE_TRANSLUCENT_FOLIAGE) && MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES + return mtsmoothstep(0.0, 0.002, x); + #else + return step(0.0, x); + #endif +} + #ifdef COLORED_SHADOWS // c_precision of 128 fits within 7 base-10 digits @@ -92,10 +149,10 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist { vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy).rgba; - float visibility = step(0.0, realDistance - texDepth.r); + float visibility = shadowCutoff(realDistance - texDepth.r); vec4 result = vec4(visibility, vec3(0.0,0.0,0.0));//unpackColor(texDepth.g)); if (visibility < 0.1) { - visibility = step(0.0, realDistance - texDepth.b); + visibility = shadowCutoff(realDistance - texDepth.b); result = vec4(visibility, unpackColor(texDepth.a)); } return result; @@ -106,7 +163,7 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) { float texDepth = texture2D(shadowsampler, smTexCoord.xy).r; - float visibility = step(0.0, realDistance - texDepth); + float visibility = shadowCutoff(realDistance - texDepth); return visibility; } @@ -378,6 +435,9 @@ void main(void) vec4 col = vec4(color.rgb * varColor.rgb, 1.0); #ifdef ENABLE_DYNAMIC_SHADOWS + // Fragment normal, can differ from vNormal which is derived from vertex normals. + vec3 fNormal = vNormal; + if (f_shadow_strength > 0.0) { float shadow_int = 0.0; vec3 shadow_color = vec3(0.0, 0.0, 0.0); @@ -414,12 +474,19 @@ void main(void) // Power ratio was measured on torches in MTG (brightness = 14). float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6); + float shadow_uncorrected = shadow_int; + // Apply self-shadowing when light falls at a narrow angle to the surface // Cosine of the cut-off angle. const float self_shadow_cutoff_cosine = 0.035; if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) { shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine); + +#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES || MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS) + // Prevents foliage from becoming insanely bright outside the shadow map. + shadow_uncorrected = mix(shadow_int, shadow_uncorrected, clamp(distance_rate * 4.0 - 3.0, 0.0, 1.0)); +#endif } shadow_int *= f_adj_shadow_strength; @@ -428,8 +495,60 @@ void main(void) col.rgb = adjusted_night_ratio * col.rgb + // artificial light (1.0 - adjusted_night_ratio) * ( // natural light - col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color + col.rgb * (1.0 - shadow_int * (1.0 - shadow_color) * (1.0 - shadow_tint)) + // filtered texture color dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight + + + vec3 reflect_ray = -normalize(v_LightDirection - fNormal * dot(v_LightDirection, fNormal) * 2.0); + + vec3 viewVec = normalize(worldPosition + cameraOffset - cameraPosition); + + // Water reflections +#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS) && ENABLE_WAVING_WATER) + vec3 wavePos = worldPosition * vec3(2.0, 0.0, 2.0); + float off = animationTimer * WATER_WAVE_SPEED * 10.0; + wavePos.x /= WATER_WAVE_LENGTH * 3.0; + wavePos.z /= WATER_WAVE_LENGTH * 2.0; + + // This is an analogous method to the bumpmap, except we get the gradient information directly from gnoise. + vec2 gradient = wave_noise(wavePos, off); + fNormal = normalize(normalize(fNormal) + vec3(gradient.x, 0., gradient.y) * WATER_WAVE_HEIGHT * abs(fNormal.y) * 0.25); + reflect_ray = -normalize(v_LightDirection - fNormal * dot(v_LightDirection, fNormal) * 2.0); + float fresnel_factor = dot(fNormal, viewVec); + + float brightness_factor = 1.0 - adjusted_night_ratio; + + // A little trig hack. We go from the dot product of viewVec and normal to the dot product of viewVec and tangent to apply a fresnel effect. + fresnel_factor = clamp(pow(1.0 - fresnel_factor * fresnel_factor, 8.0), 0.0, 1.0) * 0.8 + 0.2; + col.rgb *= 0.5; + vec3 reflection_color = mix(vec3(max(fogColor.r, max(fogColor.g, fogColor.b))), fogColor.rgb, f_shadow_strength); + + // Sky reflection + col.rgb += reflection_color * pow(fresnel_factor, 2.0) * 0.5 * brightness_factor; + vec3 water_reflect_color = 12.0 * dayLight * fresnel_factor * mtsmoothstep(0.85, 0.9, pow(clamp(dot(reflect_ray, viewVec), 0.0, 1.0), 32.0)) * max(1.0 - shadow_uncorrected, 0.0); + + // This line exists to prevent ridiculously bright reflection colors. + water_reflect_color /= clamp(max(water_reflect_color.r, max(water_reflect_color.g, water_reflect_color.b)) * 0.375, 1.0, 400.0); + col.rgb += water_reflect_color * f_adj_shadow_strength * brightness_factor; +#endif + +#if (defined(ENABLE_NODE_SPECULAR) && !defined(MATERIAL_WAVING_LIQUID)) + // Apply specular to blocks. + if (dot(v_LightDirection, vNormal) < 0.0) { + float intensity = 2.0 * (1.0 - (base.r * varColor.r)); + const float specular_exponent = 5.0; + const float fresnel_exponent = 4.0; + + col.rgb += + intensity * dayLight * (1.0 - nightRatio) * (1.0 - shadow_uncorrected) * f_adj_shadow_strength * + pow(max(dot(reflect_ray, viewVec), 0.0), fresnel_exponent) * pow(1.0 - abs(dot(viewVec, fNormal)), specular_exponent); + } +#endif + +#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES) && defined(ENABLE_TRANSLUCENT_FOLIAGE) + // Simulate translucent foliage. + col.rgb += 4.0 * dayLight * base.rgb * normalize(base.rgb * varColor.rgb * varColor.rgb) * f_adj_shadow_strength * pow(max(-dot(v_LightDirection, viewVec), 0.0), 4.0) * max(1.0 - shadow_uncorrected, 0.0); +#endif } #endif @@ -444,7 +563,13 @@ void main(void) // Note: clarity = (1 - fogginess) float clarity = clamp(fogShadingParameter - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); - col = mix(fogColor, col, clarity); + float fogColorMax = max(max(fogColor.r, fogColor.g), fogColor.b); + // Prevent zero division. + if (fogColorMax < 0.0000001) fogColorMax = 1.0; + // For high clarity (light fog) we tint the fog color. + // For this to not make the fog color artificially dark we need to normalize using the + // fog color's brightest value. We then blend our base color with this to make the fog. + col = mix(fogColor * pow(fogColor / fogColorMax, vec4(2.0 * clarity)), col, clarity); col = vec4(col.rgb, base.a); gl_FragData[0] = col; diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index d96164d76..ba48189a5 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -256,7 +256,9 @@ void main(void) z_bias *= pFactor * pFactor / f_textureresolution / f_shadowfar; shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (shadow_pos + vec4(normalOffsetScale * nNormal, 0.0))).xyz; +#if !defined(ENABLE_TRANSLUCENT_FOLIAGE) || MATERIAL_TYPE != TILE_MATERIAL_WAVING_LEAVES shadow_position.z -= z_bias; +#endif perspective_factor = pFactor; if (f_timeofday < 0.2) { diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl index 2b8af3fa9..db72d7f7d 100644 --- a/client/shaders/object_shader/opengl_fragment.glsl +++ b/client/shaders/object_shader/opengl_fragment.glsl @@ -20,6 +20,7 @@ uniform float animationTimer; uniform vec4 CameraPos; uniform float xyPerspectiveBias0; uniform float xyPerspectiveBias1; + uniform vec3 shadow_tint; varying float adj_shadow_strength; varying float cosLight; @@ -432,7 +433,7 @@ void main(void) col.rgb = adjusted_night_ratio * col.rgb + // artificial light (1.0 - adjusted_night_ratio) * ( // natural light - col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color + col.rgb * (1.0 - shadow_int * (1.0 - shadow_color) * (1.0 - shadow_tint)) + // filtered texture color dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight } #endif @@ -448,7 +449,13 @@ void main(void) // Note: clarity = (1 - fogginess) float clarity = clamp(fogShadingParameter - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); - col = mix(fogColor, col, clarity); + float fogColorMax = max(max(fogColor.r, fogColor.g), fogColor.b); + // Prevent zero division. + if (fogColorMax < 0.0000001) fogColorMax = 1.0; + // For high clarity (light fog) we tint the fog color. + // For this to not make the fog color artificially dark we need to normalize using the + // fog color's brightest value. We then blend our base color with this to make the fog. + col = mix(fogColor * pow(fogColor / fogColorMax, vec4(2.0 * clarity)), col, clarity); col = vec4(col.rgb, base.a); gl_FragData[0] = col; diff --git a/client/shaders/volumetric_light/opengl_fragment.glsl b/client/shaders/volumetric_light/opengl_fragment.glsl index 9ed5fa9ba..001f59465 100644 --- a/client/shaders/volumetric_light/opengl_fragment.glsl +++ b/client/shaders/volumetric_light/opengl_fragment.glsl @@ -46,7 +46,9 @@ float sampleVolumetricLight(vec2 uv, vec3 lightVec, float rawDepth) if (min(samplepos.x, samplepos.y) > 0. && max(samplepos.x, samplepos.y) < 1.) result += texture2D(depthmap, samplepos).r < 1. ? 0.0 : 1.0; } - return result / samples; + // We use the depth map to approximate the effect of depth on the light intensity. + // The exponent was chosen based on aesthetic preference. + return result / samples * pow(texture2D(depthmap, uv).r, 128.0); } vec3 getDirectLightScatteringAtGround(vec3 v_LightDirection) diff --git a/doc/lua_api.md b/doc/lua_api.md index b06809aee..e7cc8a07f 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8518,6 +8518,8 @@ child will follow movement and rotation of that bone. if set to zero the clouds are rendered flat. * `speed`: 2D cloud speed + direction in nodes per second (default `{x=0, z=-2}`). + * `shadow`: shadow color, applied to the base of the cloud + (default `#cccccc`). * `get_clouds()`: returns a table with the current cloud parameters as in `set_clouds`. * `override_day_night_ratio(ratio or nil)` @@ -8565,6 +8567,9 @@ child will follow movement and rotation of that bone. * `shadows` is a table that controls ambient shadows * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness) * This value has no effect on clients who have the "Dynamic Shadows" shader disabled. + * `tint` tints the shadows with the provided color, with RGB values ranging from 0 to 255. + (default `{r=0, g=0, b=0}`) + * This value has no effect on clients who have the "Dynamic Shadows" shader disabled. * `exposure` is a table that controls automatic exposure. The basic exposure factor equation is `e = 2^exposure_correction / clamp(luminance, 2^luminance_min, 2^luminance_max)` * `luminance_min` set the lower luminance boundary to use in the calculation (default: `-3.0`) diff --git a/src/client/clientevent.h b/src/client/clientevent.h index a53c007dd..acd375a4e 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -137,6 +137,7 @@ struct ClientEvent f32 density; u32 color_bright; u32 color_ambient; + u32 color_shadow; f32 height; f32 thickness; f32 speed_x; diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 3cc3564bc..d608ae2f6 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -1209,11 +1209,22 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, // Render all mesh buffers in order drawcall_count += draw_order.size(); + bool translucent_foliage = g_settings->getBool("enable_translucent_foliage"); + + video::E_MATERIAL_TYPE leaves_material = video::EMT_SOLID; + + // For translucent leaves, we want to use backface culling instead of frontface. + if (translucent_foliage) { + // this is the material leaves would use, compare to nodedef.cpp + auto* shdsrc = m_client->getShaderSource(); + const u32 leaves_shader = shdsrc->getShader("nodes_shader", TILE_MATERIAL_WAVING_LEAVES, NDT_ALLFACES); + leaves_material = shdsrc->getShaderInfo(leaves_shader).material; + } + for (auto &descriptor : draw_order) { if (!descriptor.m_reuse_material) { // override some material properties video::SMaterial local_material = descriptor.getMaterial(); - local_material.MaterialType = material.MaterialType; // do not override culling if the original material renders both back // and front faces in solid mode (e.g. plantlike) // Transparent plants would still render shadows only from one side, @@ -1222,6 +1233,11 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, local_material.BackfaceCulling = material.BackfaceCulling; local_material.FrontfaceCulling = material.FrontfaceCulling; } + if (local_material.MaterialType == leaves_material && translucent_foliage) { + local_material.BackfaceCulling = true; + local_material.FrontfaceCulling = false; + } + local_material.MaterialType = material.MaterialType; local_material.BlendOperation = material.BlendOperation; driver->setMaterial(local_material); ++material_swaps; diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index a7154c2c7..96926f3e0 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -65,6 +65,8 @@ Clouds::Clouds(scene::ISceneManager* mgr, IShaderSource *ssrc, readSettings(); g_settings->registerChangedCallback("enable_3d_clouds", &cloud_3d_setting_changed, this); + g_settings->registerChangedCallback("soft_clouds", + &cloud_3d_setting_changed, this); updateBox(); @@ -76,6 +78,8 @@ Clouds::~Clouds() { g_settings->deregisterChangedCallback("enable_3d_clouds", &cloud_3d_setting_changed, this); + g_settings->deregisterChangedCallback("soft_clouds", + &cloud_3d_setting_changed, this); } void Clouds::OnRegisterSceneNode() @@ -141,15 +145,18 @@ void Clouds::updateMesh() // shader mixes the base color, set via ColorParam c_top_f = c_side_1_f = c_side_2_f = c_bottom_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f); } - c_side_1_f.r *= 0.95f; - c_side_1_f.g *= 0.95f; - c_side_1_f.b *= 0.95f; - c_side_2_f.r *= 0.90f; - c_side_2_f.g *= 0.90f; - c_side_2_f.b *= 0.90f; - c_bottom_f.r *= 0.80f; - c_bottom_f.g *= 0.80f; - c_bottom_f.b *= 0.80f; + video::SColorf shadow = m_params.color_shadow; + + c_side_1_f.r *= shadow.r * 0.25f + 0.75f; + c_side_1_f.g *= shadow.g * 0.25f + 0.75f; + c_side_1_f.b *= shadow.b * 0.25f + 0.75f; + c_side_2_f.r *= shadow.r * 0.5f + 0.5f; + c_side_2_f.g *= shadow.g * 0.5f + 0.5f; + c_side_2_f.b *= shadow.b * 0.5f + 0.5f; + c_bottom_f.r *= shadow.r; + c_bottom_f.g *= shadow.g; + c_bottom_f.b *= shadow.b; + video::SColor c_top = c_top_f.toSColor(); video::SColor c_side_1 = c_side_1_f.toSColor(); video::SColor c_side_2 = c_side_2_f.toSColor(); @@ -221,13 +228,14 @@ void Clouds::updateMesh() const f32 ry = is3D() ? m_params.thickness * BS : 0.0f; const f32 rz = cloud_size / 2; - for(u32 i = 0; i < num_faces_to_draw; i++) + bool soft_clouds_enabled = g_settings->getBool("soft_clouds"); + for (u32 i = 0; i < num_faces_to_draw; i++) { - switch(i) + switch (i) { case 0: // top - for (video::S3DVertex &vertex : v) { - vertex.Normal.set(0,1,0); + for (video::S3DVertex& vertex : v) { + vertex.Normal.set(0, 1, 0); } v[0].Pos.set(-rx, ry,-rz); v[1].Pos.set(-rx, ry, rz); @@ -237,12 +245,20 @@ void Clouds::updateMesh() case 1: // back if (INAREA(xi, zi - 1, m_cloud_radius_i)) { u32 j = GETINDEX(xi, zi - 1, m_cloud_radius_i); - if(grid[j]) + if (grid[j]) continue; } - for (video::S3DVertex &vertex : v) { - vertex.Color = c_side_1; - vertex.Normal.set(0,0,-1); + if (soft_clouds_enabled) { + for (video::S3DVertex& vertex : v) { + vertex.Normal.set(0, 0, -1); + } + v[2].Color = c_bottom; + v[3].Color = c_bottom; + } else { + for (video::S3DVertex& vertex : v) { + vertex.Color = c_side_1; + vertex.Normal.set(0, 0, -1); + } } v[0].Pos.set(-rx, ry,-rz); v[1].Pos.set( rx, ry,-rz); @@ -251,28 +267,45 @@ void Clouds::updateMesh() break; case 2: //right if (INAREA(xi + 1, zi, m_cloud_radius_i)) { - u32 j = GETINDEX(xi+1, zi, m_cloud_radius_i); - if(grid[j]) + u32 j = GETINDEX(xi + 1, zi, m_cloud_radius_i); + if (grid[j]) continue; } - for (video::S3DVertex &vertex : v) { - vertex.Color = c_side_2; - vertex.Normal.set(1,0,0); + if (soft_clouds_enabled) { + for (video::S3DVertex& vertex : v) { + vertex.Normal.set(1, 0, 0); + } + v[2].Color = c_bottom; + v[3].Color = c_bottom; } - v[0].Pos.set( rx, ry,-rz); - v[1].Pos.set( rx, ry, rz); - v[2].Pos.set( rx, 0, rz); - v[3].Pos.set( rx, 0,-rz); + else { + for (video::S3DVertex& vertex : v) { + vertex.Color = c_side_2; + vertex.Normal.set(1, 0, 0); + } + } + v[0].Pos.set(rx, ry,-rz); + v[1].Pos.set(rx, ry, rz); + v[2].Pos.set(rx, 0, rz); + v[3].Pos.set(rx, 0,-rz); break; case 3: // front if (INAREA(xi, zi + 1, m_cloud_radius_i)) { u32 j = GETINDEX(xi, zi + 1, m_cloud_radius_i); - if(grid[j]) + if (grid[j]) continue; } - for (video::S3DVertex &vertex : v) { - vertex.Color = c_side_1; - vertex.Normal.set(0,0,-1); + if (soft_clouds_enabled) { + for (video::S3DVertex& vertex : v) { + vertex.Normal.set(0, 0, -1); + } + v[2].Color = c_bottom; + v[3].Color = c_bottom; + } else { + for (video::S3DVertex& vertex : v) { + vertex.Color = c_side_1; + vertex.Normal.set(0, 0, -1); + } } v[0].Pos.set( rx, ry, rz); v[1].Pos.set(-rx, ry, rz); @@ -280,14 +313,22 @@ void Clouds::updateMesh() v[3].Pos.set( rx, 0, rz); break; case 4: // left - if (INAREA(xi-1, zi, m_cloud_radius_i)) { - u32 j = GETINDEX(xi-1, zi, m_cloud_radius_i); - if(grid[j]) + if (INAREA(xi - 1, zi, m_cloud_radius_i)) { + u32 j = GETINDEX(xi - 1, zi, m_cloud_radius_i); + if (grid[j]) continue; } - for (video::S3DVertex &vertex : v) { - vertex.Color = c_side_2; - vertex.Normal.set(-1,0,0); + if (soft_clouds_enabled) { + for (video::S3DVertex& vertex : v) { + vertex.Normal.set(-1, 0, 0); + } + v[2].Color = c_bottom; + v[3].Color = c_bottom; + } else { + for (video::S3DVertex& vertex : v) { + vertex.Color = c_side_2; + vertex.Normal.set(-1, 0, 0); + } } v[0].Pos.set(-rx, ry, rz); v[1].Pos.set(-rx, ry,-rz); @@ -295,9 +336,9 @@ void Clouds::updateMesh() v[3].Pos.set(-rx, 0, rz); break; case 5: // bottom - for (video::S3DVertex &vertex : v) { + for (video::S3DVertex& vertex : v) { vertex.Color = c_bottom; - vertex.Normal.set(0,-1,0); + vertex.Normal.set(0, -1, 0); } v[0].Pos.set( rx, 0, rz); v[1].Pos.set(-rx, 0, rz); diff --git a/src/client/clouds.h b/src/client/clouds.h index acd4b0cfb..7193c03f9 100644 --- a/src/client/clouds.h +++ b/src/client/clouds.h @@ -109,6 +109,14 @@ public: m_params.color_ambient = color_ambient; } + void setColorShadow(video::SColor color_shadow) + { + if (m_params.color_shadow == color_shadow) + return; + m_params.color_shadow = color_shadow; + invalidateMesh(); + } + void setHeight(float height) { if (m_params.height == height) diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index f7852ad1c..2a1352139 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -83,7 +83,8 @@ MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector meshmanip(mm), blockpos_nodes(data->m_blockpos * MAP_BLOCKSIZE), enable_mesh_cache(g_settings->getBool("enable_mesh_cache") && - !data->m_smooth_lighting) // Mesh cache is not supported with smooth lighting + !data->m_smooth_lighting), // Mesh cache is not supported with smooth lighting + smooth_liquids(g_settings->getBool("enable_water_reflections")) { } @@ -717,7 +718,7 @@ void MapblockMeshGenerator::drawLiquidSides() if (data->m_smooth_lighting) cur_node.color = blendLightColor(pos); pos += cur_node.origin; - vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, cur_node.color, vertex.u, v); + vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, face.dir.X, face.dir.Y, face.dir.Z, cur_node.color, vertex.u, v); }; collector->append(cur_liquid.tile, vertices, 4, quad_indices, 6); } @@ -740,6 +741,19 @@ void MapblockMeshGenerator::drawLiquidTop() for (int i = 0; i < 4; i++) { int u = corner_resolve[i][0]; int w = corner_resolve[i][1]; + + if (smooth_liquids) { + int x = vertices[i].Pos.X > 0; + int z = vertices[i].Pos.Z > 0; + + f32 dx = 0.5f * (cur_liquid.neighbors[z][x].level - cur_liquid.neighbors[z][x + 1].level + + cur_liquid.neighbors[z + 1][x].level - cur_liquid.neighbors[z + 1][x + 1].level); + f32 dz = 0.5f * (cur_liquid.neighbors[z][x].level - cur_liquid.neighbors[z + 1][x].level + + cur_liquid.neighbors[z][x + 1].level - cur_liquid.neighbors[z + 1][x + 1].level); + + vertices[i].Normal = v3f(dx, 1., dz).normalize(); + } + vertices[i].Pos.Y += cur_liquid.corner_levels[w][u] * BS; if (data->m_smooth_lighting) vertices[i].Color = blendLightColor(vertices[i].Pos); @@ -779,6 +793,10 @@ void MapblockMeshGenerator::drawLiquidTop() vertex.TCoords += tcoord_center; vertex.TCoords += tcoord_translate; + + if (!smooth_liquids) { + vertex.Normal = v3f(dx, 1., dz).normalize(); + } } std::swap(vertices[0].TCoords, vertices[2].TCoords); diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 730330a03..1c3060c83 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -134,6 +134,7 @@ private: f32 corner_levels[2][2]; }; LiquidData cur_liquid; + bool smooth_liquids = false; void prepareLiquidNodeDrawing(); void getLiquidNeighborhood(); diff --git a/src/client/game.cpp b/src/client/game.cpp index 33475f338..ed593abba 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -386,6 +386,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_minimap_yaw{"yawVec"}; CachedPixelShaderSetting m_camera_offset_pixel{"cameraOffset"}; CachedVertexShaderSetting m_camera_offset_vertex{"cameraOffset"}; + CachedPixelShaderSetting m_camera_position_pixel{ "cameraPosition" }; + CachedVertexShaderSetting m_camera_position_vertex{ "cameraPosition" }; CachedPixelShaderSetting m_texture0{"texture0"}; CachedPixelShaderSetting m_texture1{"texture1"}; CachedPixelShaderSetting m_texture2{"texture2"}; @@ -492,6 +494,10 @@ public: m_camera_offset_pixel.set(offset, services); m_camera_offset_vertex.set(offset, services); + v3f camera_position = m_client->getCamera()->getPosition(); + m_camera_position_pixel.set(camera_position, services); + m_camera_position_pixel.set(camera_position, services); + SamplerLayer_t tex_id; tex_id = 0; m_texture0.set(&tex_id, services); @@ -3184,6 +3190,7 @@ void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation * clouds->setDensity(event->cloud_params.density); clouds->setColorBright(video::SColor(event->cloud_params.color_bright)); clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient)); + clouds->setColorShadow(video::SColor(event->cloud_params.color_shadow)); clouds->setHeight(event->cloud_params.height); clouds->setThickness(event->cloud_params.thickness); clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y)); @@ -4315,7 +4322,9 @@ void Game::updateShadows() float timeoftheday = getWickedTimeOfDay(in_timeofday); bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75; bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible(); - shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f); + const auto &lighting = client->getEnv().getLocalPlayer()->getLighting(); + shadow->setShadowIntensity(is_shadow_visible ? lighting.shadow_intensity : 0.0f); + shadow->setShadowTint(lighting.shadow_tint); timeoftheday = std::fmod(timeoftheday + 0.75f, 0.5f) + 0.25f; const float offset_constant = 10000.0f; diff --git a/src/client/shader.cpp b/src/client/shader.cpp index f43bc27dc..ad37ea4c1 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -689,6 +689,15 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, if (g_settings->getBool("shadow_poisson_filter")) shaders_header << "#define POISSON_FILTER 1\n"; + if (g_settings->getBool("enable_water_reflections")) + shaders_header << "#define ENABLE_WATER_REFLECTIONS 1\n"; + + if (g_settings->getBool("enable_translucent_foliage")) + shaders_header << "#define ENABLE_TRANSLUCENT_FOLIAGE 1\n"; + + if (g_settings->getBool("enable_node_specular")) + shaders_header << "#define ENABLE_NODE_SPECULAR 1\n"; + s32 shadow_filter = g_settings->getS32("shadow_filters"); shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n"; diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index c4ffb39e2..d4437be68 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -94,9 +94,11 @@ public: bool is_active() const { return m_shadows_enabled && shadowMapTextureFinal != nullptr; } void setTimeOfDay(float isDay) { m_time_day = isDay; }; void setShadowIntensity(float shadow_intensity); + void setShadowTint(video::SColor shadow_tint) { m_shadow_tint = shadow_tint; } s32 getShadowSamples() const { return m_shadow_samples; } float getShadowStrength() const { return m_shadows_enabled ? m_shadow_strength : 0.0f; } + video::SColor getShadowTint() const { return m_shadow_tint; } float getTimeOfDay() const { return m_time_day; } f32 getPerspectiveBiasXY() { return m_perspective_bias_xy; } @@ -131,6 +133,7 @@ private: std::vector m_shadow_node_array; float m_shadow_strength; + video::SColor m_shadow_tint{ 255, 0, 0, 0 }; float m_shadow_strength_gamma; float m_shadow_map_max_distance; float m_shadow_map_texture_size; diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp index 32d3e36be..fc4f73186 100644 --- a/src/client/shadows/shadowsshadercallbacks.cpp +++ b/src/client/shadows/shadowsshadercallbacks.cpp @@ -40,6 +40,9 @@ void ShadowConstantSetter::onSetConstants(video::IMaterialRendererServices *serv f32 ShadowStrength = shadow->getShadowStrength(); m_shadow_strength.set(&ShadowStrength, services); + video::SColor ShadowTint = shadow->getShadowTint(); + m_shadow_tint.set(ShadowTint, services); + f32 timeOfDay = shadow->getTimeOfDay(); m_time_of_day.set(&timeOfDay, services); diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h index af83f021e..beac25d72 100644 --- a/src/client/shadows/shadowsshadercallbacks.h +++ b/src/client/shadows/shadowsshadercallbacks.h @@ -31,6 +31,7 @@ class ShadowConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_light_direction{"v_LightDirection"}; CachedPixelShaderSetting m_texture_res{"f_textureresolution"}; CachedPixelShaderSetting m_shadow_strength{"f_shadow_strength"}; + CachedPixelShaderSetting m_shadow_tint{ "shadow_tint" }; CachedPixelShaderSetting m_time_of_day{"f_timeofday"}; CachedPixelShaderSetting m_shadowfar{"f_shadowfar"}; CachedPixelShaderSetting m_camera_pos{"CameraPos"}; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 8e2411df5..12946b06d 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -275,6 +275,7 @@ void set_default_settings() settings->setDefault("view_bobbing_amount", "1.0"); settings->setDefault("fall_bobbing_amount", "0.03"); settings->setDefault("enable_3d_clouds", "true"); + settings->setDefault("soft_clouds", "false"); settings->setDefault("cloud_radius", "12"); settings->setDefault("menu_clouds", "true"); settings->setDefault("translucent_liquids", "true"); @@ -335,6 +336,9 @@ void set_default_settings() settings->setDefault("bloom_intensity", "0.05"); settings->setDefault("bloom_radius", "1"); settings->setDefault("enable_volumetric_lighting", "false"); + settings->setDefault("enable_water_reflections", "false"); + settings->setDefault("enable_translucent_foliage", "false"); + settings->setDefault("enable_node_specular", "false"); // Effects Shadows settings->setDefault("enable_dynamic_shadows", "false"); diff --git a/src/lighting.h b/src/lighting.h index 262a48b5d..20f434112 100644 --- a/src/lighting.h +++ b/src/lighting.h @@ -18,7 +18,9 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #pragma once +#include "SColor.h" +using namespace irr; /** * Parameters for automatic exposure compensation @@ -54,4 +56,5 @@ struct Lighting float shadow_intensity {0.0f}; float saturation {1.0f}; float volumetric_light_strength {0.0f}; + video::SColor shadow_tint; }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index a1bffbb93..5b7c2f527 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1476,6 +1476,7 @@ void Client::handleCommand_CloudParams(NetworkPacket* pkt) f32 density; video::SColor color_bright; video::SColor color_ambient; + video::SColor color_shadow = video::SColor(255, 204, 204, 204); f32 height; f32 thickness; v2f speed; @@ -1483,6 +1484,10 @@ void Client::handleCommand_CloudParams(NetworkPacket* pkt) *pkt >> density >> color_bright >> color_ambient >> height >> thickness >> speed; + if (pkt->getRemainingBytes() >= 4) { + *pkt >> color_shadow; + } + ClientEvent *event = new ClientEvent(); event->type = CE_CLOUD_PARAMS; event->cloud_params.density = density; @@ -1491,6 +1496,7 @@ void Client::handleCommand_CloudParams(NetworkPacket* pkt) // we avoid using new() and delete() for no good reason event->cloud_params.color_bright = color_bright.color; event->cloud_params.color_ambient = color_ambient.color; + event->cloud_params.color_shadow = color_shadow.color; event->cloud_params.height = height; event->cloud_params.thickness = thickness; // same here: deconstruct to skip constructor @@ -1821,4 +1827,6 @@ void Client::handleCommand_SetLighting(NetworkPacket *pkt) } if (pkt->getRemainingBytes() >= 4) *pkt >> lighting.volumetric_light_strength; + if (pkt->getRemainingBytes() >= 4) + *pkt >> lighting.shadow_tint; } diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 5af9859ae..85931c71b 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -229,10 +229,13 @@ with this program; if not, write to the Free Software Foundation, Inc., [bump for 5.9.1] PROTOCOL VERSION 46: Move default hotbar from client-side C++ to server-side builtin Lua + Add shadow tint to Lighting packets + Add shadow color to CloudParam packets [scheduled bump for 5.10.0] */ #define LATEST_PROTOCOL_VERSION 46 + #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) // Server's supported network protocol range @@ -1176,4 +1179,4 @@ enum InteractAction : u8 INTERACT_PLACE, // 3: place block or item (to abovesurface) INTERACT_USE, // 4: use item INTERACT_ACTIVATE // 5: rightclick air ("activate") -}; +}; \ No newline at end of file diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index db7212897..d6d608aab 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2448,6 +2448,10 @@ int ObjectRef::l_set_clouds(lua_State *L) if (!lua_isnil(L, -1)) read_color(L, -1, &cloud_params.color_ambient); lua_pop(L, 1); + lua_getfield(L, 2, "shadow"); + if (!lua_isnil(L, -1)) + read_color(L, -1, &cloud_params.color_shadow); + lua_pop(L, 1); cloud_params.height = getfloatfield_default(L, 2, "height", cloud_params.height); cloud_params.thickness = getfloatfield_default(L, 2, "thickness", cloud_params.thickness); @@ -2483,6 +2487,8 @@ int ObjectRef::l_get_clouds(lua_State *L) lua_setfield(L, -2, "color"); push_ARGB8(L, cloud_params.color_ambient); lua_setfield(L, -2, "ambient"); + push_ARGB8(L, cloud_params.color_shadow); + lua_setfield(L, -2, "shadow"); lua_pushnumber(L, cloud_params.height); lua_setfield(L, -2, "height"); lua_pushnumber(L, cloud_params.thickness); @@ -2611,6 +2617,8 @@ int ObjectRef::l_set_lighting(lua_State *L) lua_getfield(L, 2, "shadows"); if (lua_istable(L, -1)) { getfloatfield(L, -1, "intensity", lighting.shadow_intensity); + lua_getfield(L, -1, "tint"); + read_color(L, -1, &lighting.shadow_tint); } lua_pop(L, 1); // shadows @@ -2654,6 +2662,8 @@ int ObjectRef::l_get_lighting(lua_State *L) lua_newtable(L); // "shadows" lua_pushnumber(L, lighting.shadow_intensity); lua_setfield(L, -2, "intensity"); + push_ARGB8(L, lighting.shadow_tint); + lua_setfield(L, -2, "tint"); lua_setfield(L, -2, "shadows"); lua_pushnumber(L, lighting.saturation); lua_setfield(L, -2, "saturation"); diff --git a/src/server.cpp b/src/server.cpp index 6421fc175..4be3aa26a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1843,7 +1843,7 @@ void Server::SendCloudParams(session_t peer_id, const CloudParams ¶ms) { NetworkPacket pkt(TOCLIENT_CLOUD_PARAMS, 0, peer_id); pkt << params.density << params.color_bright << params.color_ambient - << params.height << params.thickness << params.speed; + << params.height << params.thickness << params.speed << params.color_shadow; Send(&pkt); } @@ -1873,7 +1873,7 @@ void Server::SendSetLighting(session_t peer_id, const Lighting &lighting) << lighting.exposure.speed_bright_dark << lighting.exposure.center_weight_power; - pkt << lighting.volumetric_light_strength; + pkt << lighting.volumetric_light_strength << lighting.shadow_tint; Send(&pkt); } diff --git a/src/skyparams.h b/src/skyparams.h index 2ff918f36..52a15810b 100644 --- a/src/skyparams.h +++ b/src/skyparams.h @@ -81,6 +81,7 @@ struct CloudParams float density; video::SColor color_bright; video::SColor color_ambient; + video::SColor color_shadow; float thickness; float height; v2f speed; @@ -160,6 +161,7 @@ public: clouds.density = 0.4f; clouds.color_bright = video::SColor(229, 240, 240, 255); clouds.color_ambient = video::SColor(255, 0, 0, 0); + clouds.color_shadow = video::SColor(255, 204, 204, 204); clouds.thickness = 16.0f; clouds.height = 120; clouds.speed = v2f(0.0f, -2.0f); From f65fe80e8120e1052ef4c00c423cd5038b4b71bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20=C3=85str=C3=B6m?= Date: Tue, 24 Sep 2024 22:25:34 +0200 Subject: [PATCH 131/200] Add minetest.bulk_swap_node (#15043) Co-authored-by: sfan5 --- builtin/emerge/env.lua | 2 ++ doc/lua_api.md | 2 ++ games/devtest/mods/benchmarks/init.lua | 33 ++++++++++++++++++++++++++ src/script/lua_api/l_env.cpp | 26 ++++++++++++++++++++ src/script/lua_api/l_env.h | 4 ++++ 5 files changed, 67 insertions(+) diff --git a/builtin/emerge/env.lua b/builtin/emerge/env.lua index 2b32a0339..5beb6285c 100644 --- a/builtin/emerge/env.lua +++ b/builtin/emerge/env.lua @@ -23,6 +23,8 @@ core.add_node = core.set_node -- we don't deal with metadata currently core.swap_node = core.set_node +core.bulk_swap_node = core.bulk_set_node + function core.remove_node(pos) return core.vmanip:set_node_at(pos, {name="air"}) end diff --git a/doc/lua_api.md b/doc/lua_api.md index e7cc8a07f..3a5f7e128 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6145,6 +6145,8 @@ Environment access * `minetest.swap_node(pos, node)` * Swap node at position with another. * This keeps the metadata intact and will not run con-/destructor callbacks. +* `minetest.bulk_swap_node({pos1, pos2, pos3, ...}, node)` + * Equivalent to `minetest.swap_node` but in bulk. * `minetest.remove_node(pos)`: Remove a node * Equivalent to `minetest.set_node(pos, {name="air"})`, but a bit faster. * `minetest.get_node(pos)` diff --git a/games/devtest/mods/benchmarks/init.lua b/games/devtest/mods/benchmarks/init.lua index 1f5001c69..e3a4409a5 100644 --- a/games/devtest/mods/benchmarks/init.lua +++ b/games/devtest/mods/benchmarks/init.lua @@ -154,3 +154,36 @@ minetest.register_chatcommand("bench_bulk_get_node", { return true, msg end, }) + +minetest.register_chatcommand("bench_bulk_swap_node", { + params = "", + description = "Benchmark: Bulk-swap 99×99×99 stone nodes", + func = function(name, param) + local player = minetest.get_player_by_name(name) + if not player then + return false, "No player." + end + local pos_list = get_positions_cube(player:get_pos()) + + minetest.chat_send_player(name, "Benchmarking minetest.bulk_swap_node. Warming up ...") + + -- warm up because first execution otherwise becomes + -- significantly slower + minetest.bulk_swap_node(pos_list, {name = "mapgen_stone"}) + + minetest.chat_send_player(name, "Warming up finished, now benchmarking ...") + + local start_time = minetest.get_us_time() + for i=1,#pos_list do + minetest.swap_node(pos_list[i], {name = "mapgen_stone"}) + end + local middle_time = minetest.get_us_time() + minetest.bulk_swap_node(pos_list, {name = "mapgen_stone"}) + local end_time = minetest.get_us_time() + local msg = string.format("Benchmark results: minetest.swap_node loop: %.2f ms; minetest.bulk_swap_node: %.2f ms", + ((middle_time - start_time)) / 1000, + ((end_time - middle_time)) / 1000 + ) + return true, msg + end, +}) diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index a024d7e42..125a352bc 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -253,6 +253,31 @@ int ModApiEnv::l_swap_node(lua_State *L) return 1; } +// bulk_swap_node([pos1, pos2, ...], node) +// pos = {x=num, y=num, z=num} +int ModApiEnv::l_bulk_swap_node(lua_State *L) +{ + GET_ENV_PTR; + + luaL_checktype(L, 1, LUA_TTABLE); + + s32 len = lua_objlen(L, 1); + + MapNode n = readnode(L, 2); + + // Do it + bool succeeded = true; + for (s32 i = 1; i <= len; i++) { + lua_rawgeti(L, 1, i); + if (!env->swapNode(read_v3s16(L, -1), n)) + succeeded = false; + lua_pop(L, 1); + } + + lua_pushboolean(L, succeeded); + return 1; +} + // get_node_raw(x, y, z) -> content, param1, param2, pos_ok int ModApiEnv::l_get_node_raw(lua_State *L) { @@ -1377,6 +1402,7 @@ void ModApiEnv::Initialize(lua_State *L, int top) API_FCT(bulk_set_node); API_FCT(add_node); API_FCT(swap_node); + API_FCT(bulk_swap_node); API_FCT(add_item); API_FCT(remove_node); API_FCT(get_node_raw); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index b92a6f935..ba0f2eb61 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -65,6 +65,10 @@ private: // pos = {x=num, y=num, z=num} static int l_bulk_set_node(lua_State *L); + // bulk_swap_node([pos1, pos2, ...], node) + // pos = {x=num, y=num, z=num} + static int l_bulk_swap_node(lua_State *L); + static int l_add_node(lua_State *L); // remove_node(pos) From 3c48671076e7ae3d33b507b1f6d239a47ab6501a Mon Sep 17 00:00:00 2001 From: grorp Date: Tue, 24 Sep 2024 22:25:46 +0200 Subject: [PATCH 132/200] Fix -Winconsistent-missing-override in unit_sao.h (#15190) --- src/server/unit_sao.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/server/unit_sao.h b/src/server/unit_sao.h index c7f8c4aec..32b5dd30e 100644 --- a/src/server/unit_sao.h +++ b/src/server/unit_sao.h @@ -31,7 +31,7 @@ public: UnitSAO(ServerEnvironment *env, v3f pos); virtual ~UnitSAO() = default; - u16 getHP() const { return m_hp; } + u16 getHP() const override { return m_hp; } // Use a function, if isDead can be defined by other conditions bool isDead() const { return m_hp == 0; } @@ -59,39 +59,39 @@ public: { return itemgroup_get(getArmorGroups(), "immortal"); } - void setArmorGroups(const ItemGroupList &armor_groups); - const ItemGroupList &getArmorGroups() const; + void setArmorGroups(const ItemGroupList &armor_groups) override; + const ItemGroupList &getArmorGroups() const override; // Animation void setAnimation(v2f frame_range, float frame_speed, float frame_blend, - bool frame_loop); + bool frame_loop) override; void getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, - bool *frame_loop); - void setAnimationSpeed(float frame_speed); + bool *frame_loop) override; + void setAnimationSpeed(float frame_speed) override; // Bone position - void setBoneOverride(const std::string &bone, const BoneOverride &props); - BoneOverride getBoneOverride(const std::string &bone); + void setBoneOverride(const std::string &bone, const BoneOverride &props) override; + BoneOverride getBoneOverride(const std::string &bone) override; const std::unordered_map - &getBoneOverrides() const { return m_bone_override; }; + &getBoneOverrides() const override { return m_bone_override; }; // Attachments - ServerActiveObject *getParent() const; + ServerActiveObject *getParent() const override; inline bool isAttached() const { return m_attachment_parent_id != 0; } void setAttachment(object_t parent_id, const std::string &bone, v3f position, - v3f rotation, bool force_visible); + v3f rotation, bool force_visible) override; void getAttachment(object_t *parent_id, std::string *bone, v3f *position, - v3f *rotation, bool *force_visible) const; + v3f *rotation, bool *force_visible) const override; void clearChildAttachments() override; void addAttachmentChild(object_t child_id) override; void removeAttachmentChild(object_t child_id) override; - const std::unordered_set &getAttachmentChildIds() const { + const std::unordered_set &getAttachmentChildIds() const override { return m_attachment_child_ids; } // Object properties - ObjectProperties *accessObjectProperties(); - void notifyObjectPropertiesModified(); + ObjectProperties *accessObjectProperties() override; + void notifyObjectPropertiesModified() override; void sendOutdatedData(); // Update packets @@ -125,11 +125,11 @@ protected: object_t m_attachment_parent_id = 0; void clearAnyAttachments(); - virtual void onMarkedForDeactivation() { + virtual void onMarkedForDeactivation() override { ServerActiveObject::onMarkedForDeactivation(); clearAnyAttachments(); } - virtual void onMarkedForRemoval() { + virtual void onMarkedForRemoval() override { ServerActiveObject::onMarkedForRemoval(); clearAnyAttachments(); } From 526a2f7b8c45504088e194a83d54a19045227bbd Mon Sep 17 00:00:00 2001 From: grorp Date: Tue, 24 Sep 2024 22:37:44 +0200 Subject: [PATCH 133/200] Dehardcode the death formspec (#15155) Co-authored-by: Paul Ouellette --- builtin/client/death_formspec.lua | 15 ------------ builtin/client/init.lua | 1 - builtin/game/death_screen.lua | 31 +++++++++++++++++++++++++ builtin/game/init.lua | 1 + builtin/locale/__builtin.be.tr | 3 +++ builtin/locale/__builtin.bg.tr | 3 +++ builtin/locale/__builtin.ca.tr | 3 +++ builtin/locale/__builtin.cs.tr | 3 +++ builtin/locale/__builtin.cy.tr | 3 +++ builtin/locale/__builtin.da.tr | 3 +++ builtin/locale/__builtin.de.tr | 2 ++ builtin/locale/__builtin.el.tr | 3 +++ builtin/locale/__builtin.eo.tr | 2 ++ builtin/locale/__builtin.es.tr | 3 +++ builtin/locale/__builtin.et.tr | 3 +++ builtin/locale/__builtin.eu.tr | 3 +++ builtin/locale/__builtin.fi.tr | 3 +++ builtin/locale/__builtin.fil.tr | 3 +++ builtin/locale/__builtin.fr.tr | 2 ++ builtin/locale/__builtin.ga.tr | 3 +++ builtin/locale/__builtin.gl.tr | 3 +++ builtin/locale/__builtin.hu.tr | 3 +++ builtin/locale/__builtin.id.tr | 2 ++ builtin/locale/__builtin.it.tr | 2 ++ builtin/locale/__builtin.ja.tr | 3 +++ builtin/locale/__builtin.jbo.tr | 3 +++ builtin/locale/__builtin.jv.tr | 3 +++ builtin/locale/__builtin.ko.tr | 3 +++ builtin/locale/__builtin.kv.tr | 3 +++ builtin/locale/__builtin.ky.tr | 3 +++ builtin/locale/__builtin.lt.tr | 3 +++ builtin/locale/__builtin.lv.tr | 3 +++ builtin/locale/__builtin.lzh.tr | 3 +++ builtin/locale/__builtin.mn.tr | 3 +++ builtin/locale/__builtin.mr.tr | 3 +++ builtin/locale/__builtin.ms.tr | 2 ++ builtin/locale/__builtin.nb.tr | 3 +++ builtin/locale/__builtin.nl.tr | 3 +++ builtin/locale/__builtin.nn.tr | 3 +++ builtin/locale/__builtin.oc.tr | 3 +++ builtin/locale/__builtin.pl.tr | 3 +++ builtin/locale/__builtin.pt.tr | 3 +++ builtin/locale/__builtin.pt_BR.tr | 2 ++ builtin/locale/__builtin.ro.tr | 3 +++ builtin/locale/__builtin.ru.tr | 2 ++ builtin/locale/__builtin.sk.tr | 3 +++ builtin/locale/__builtin.sl.tr | 3 +++ builtin/locale/__builtin.sr_Cyrl.tr | 3 +++ builtin/locale/__builtin.sr_Latn.tr | 3 +++ builtin/locale/__builtin.sv.tr | 3 +++ builtin/locale/__builtin.sw.tr | 3 +++ builtin/locale/__builtin.tok.tr | 3 +++ builtin/locale/__builtin.tr.tr | 3 +++ builtin/locale/__builtin.tt.tr | 3 +++ builtin/locale/__builtin.uk.tr | 3 +++ builtin/locale/__builtin.vi.tr | 3 +++ builtin/locale/__builtin.zh_CN.tr | 3 +++ builtin/locale/__builtin.zh_TW.tr | 3 +++ doc/client_lua_api.md | 4 ---- doc/lua_api.md | 8 +++++++ src/client/client.cpp | 4 ++-- src/client/client.h | 4 ++-- src/client/clientevent.h | 9 +------- src/client/game.cpp | 25 ++++++-------------- src/network/clientopcodes.cpp | 4 ++-- src/network/clientpackethandler.cpp | 14 ++--------- src/network/networkprotocol.h | 17 ++++++++------ src/network/serveropcodes.cpp | 4 ++-- src/network/serverpackethandler.cpp | 27 ---------------------- src/script/cpp_api/s_client.cpp | 15 ------------ src/script/cpp_api/s_client.h | 1 - src/script/lua_api/l_client.cpp | 8 ------- src/script/lua_api/l_client.h | 3 --- src/script/lua_api/l_object.cpp | 6 ++--- src/server.cpp | 36 ----------------------------- src/server.h | 5 ---- src/server/clientiface.h | 2 +- src/server/player_sao.cpp | 15 ++++++++++++ src/server/player_sao.h | 1 + 79 files changed, 242 insertions(+), 172 deletions(-) delete mode 100644 builtin/client/death_formspec.lua create mode 100644 builtin/game/death_screen.lua create mode 100644 builtin/locale/__builtin.be.tr create mode 100644 builtin/locale/__builtin.bg.tr create mode 100644 builtin/locale/__builtin.ca.tr create mode 100644 builtin/locale/__builtin.cs.tr create mode 100644 builtin/locale/__builtin.cy.tr create mode 100644 builtin/locale/__builtin.da.tr create mode 100644 builtin/locale/__builtin.el.tr create mode 100644 builtin/locale/__builtin.es.tr create mode 100644 builtin/locale/__builtin.et.tr create mode 100644 builtin/locale/__builtin.eu.tr create mode 100644 builtin/locale/__builtin.fi.tr create mode 100644 builtin/locale/__builtin.fil.tr create mode 100644 builtin/locale/__builtin.ga.tr create mode 100644 builtin/locale/__builtin.gl.tr create mode 100644 builtin/locale/__builtin.hu.tr create mode 100644 builtin/locale/__builtin.ja.tr create mode 100644 builtin/locale/__builtin.jbo.tr create mode 100644 builtin/locale/__builtin.jv.tr create mode 100644 builtin/locale/__builtin.ko.tr create mode 100644 builtin/locale/__builtin.kv.tr create mode 100644 builtin/locale/__builtin.ky.tr create mode 100644 builtin/locale/__builtin.lt.tr create mode 100644 builtin/locale/__builtin.lv.tr create mode 100644 builtin/locale/__builtin.lzh.tr create mode 100644 builtin/locale/__builtin.mn.tr create mode 100644 builtin/locale/__builtin.mr.tr create mode 100644 builtin/locale/__builtin.nb.tr create mode 100644 builtin/locale/__builtin.nl.tr create mode 100644 builtin/locale/__builtin.nn.tr create mode 100644 builtin/locale/__builtin.oc.tr create mode 100644 builtin/locale/__builtin.pl.tr create mode 100644 builtin/locale/__builtin.pt.tr create mode 100644 builtin/locale/__builtin.ro.tr create mode 100644 builtin/locale/__builtin.sk.tr create mode 100644 builtin/locale/__builtin.sl.tr create mode 100644 builtin/locale/__builtin.sr_Cyrl.tr create mode 100644 builtin/locale/__builtin.sr_Latn.tr create mode 100644 builtin/locale/__builtin.sv.tr create mode 100644 builtin/locale/__builtin.sw.tr create mode 100644 builtin/locale/__builtin.tok.tr create mode 100644 builtin/locale/__builtin.tr.tr create mode 100644 builtin/locale/__builtin.tt.tr create mode 100644 builtin/locale/__builtin.uk.tr create mode 100644 builtin/locale/__builtin.vi.tr create mode 100644 builtin/locale/__builtin.zh_CN.tr create mode 100644 builtin/locale/__builtin.zh_TW.tr diff --git a/builtin/client/death_formspec.lua b/builtin/client/death_formspec.lua deleted file mode 100644 index c25c799ab..000000000 --- a/builtin/client/death_formspec.lua +++ /dev/null @@ -1,15 +0,0 @@ --- CSM death formspec. Only used when clientside modding is enabled, otherwise --- handled by the engine. - -core.register_on_death(function() - local formspec = "size[11,5.5]bgcolor[#320000b4;true]" .. - "label[4.85,1.35;" .. fgettext("You died") .. - "]button_exit[4,3;3,0.5;btn_respawn;".. fgettext("Respawn") .."]" - core.show_formspec("bultin:death", formspec) -end) - -core.register_on_formspec_input(function(formname, fields) - if formname == "bultin:death" then - core.send_respawn() - end -end) diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 301a8050c..8d01c99a4 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -9,6 +9,5 @@ dofile(commonpath .. "mod_storage.lua") dofile(commonpath .. "chatcommands.lua") dofile(commonpath .. "information_formspecs.lua") dofile(clientpath .. "chatcommands.lua") -dofile(clientpath .. "death_formspec.lua") dofile(clientpath .. "misc.lua") assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions diff --git a/builtin/game/death_screen.lua b/builtin/game/death_screen.lua new file mode 100644 index 000000000..77f56aaff --- /dev/null +++ b/builtin/game/death_screen.lua @@ -0,0 +1,31 @@ +local F = core.formspec_escape +local S = core.get_translator("__builtin") + +function core.show_death_screen(player, _reason) + local fs = { + "formspec_version[1]", + "size[11,5.5,true]", + "bgcolor[#320000b4;true]", + "label[4.85,1.35;", F(S("You died")), "]", + "button_exit[4,3;3,0.5;btn_respawn;", F(S("Respawn")), "]", + } + core.show_formspec(player:get_player_name(), "__builtin:death", table.concat(fs, "")) +end + +core.register_on_dieplayer(function(player, reason) + core.show_death_screen(player, reason) +end) + +core.register_on_joinplayer(function(player) + if player:get_hp() == 0 then + core.show_death_screen(player, nil) + end +end) + +core.register_on_player_receive_fields(function(player, formname, fields) + if formname == "__builtin:death" and fields.quit and player:get_hp() == 0 then + player:respawn() + core.log("action", player:get_player_name() .. " respawns at " .. + player:get_pos():to_string()) + end +end) diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 4e16c686d..b3c64e729 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -38,6 +38,7 @@ dofile(gamepath .. "forceloading.lua") dofile(gamepath .. "hud.lua") dofile(gamepath .. "knockback.lua") dofile(gamepath .. "async.lua") +dofile(gamepath .. "death_screen.lua") core.after(0, builtin_shared.cache_content_ids) diff --git a/builtin/locale/__builtin.be.tr b/builtin/locale/__builtin.be.tr new file mode 100644 index 000000000..bd8ea999e --- /dev/null +++ b/builtin/locale/__builtin.be.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Вы загінулі +Respawn=Адрадзіцца diff --git a/builtin/locale/__builtin.bg.tr b/builtin/locale/__builtin.bg.tr new file mode 100644 index 000000000..43fe8f31e --- /dev/null +++ b/builtin/locale/__builtin.bg.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Умряхте +Respawn=Прераждане diff --git a/builtin/locale/__builtin.ca.tr b/builtin/locale/__builtin.ca.tr new file mode 100644 index 000000000..5cde155f9 --- /dev/null +++ b/builtin/locale/__builtin.ca.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Has mort +Respawn=Reaparèixer diff --git a/builtin/locale/__builtin.cs.tr b/builtin/locale/__builtin.cs.tr new file mode 100644 index 000000000..4f4b592e4 --- /dev/null +++ b/builtin/locale/__builtin.cs.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Zemřel jsi +Respawn=Oživit diff --git a/builtin/locale/__builtin.cy.tr b/builtin/locale/__builtin.cy.tr new file mode 100644 index 000000000..372da1a89 --- /dev/null +++ b/builtin/locale/__builtin.cy.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Buest ti farw +Respawn=Atgyfodi diff --git a/builtin/locale/__builtin.da.tr b/builtin/locale/__builtin.da.tr new file mode 100644 index 000000000..c34eceeb9 --- /dev/null +++ b/builtin/locale/__builtin.da.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Du døde +Respawn=Genopstå diff --git a/builtin/locale/__builtin.de.tr b/builtin/locale/__builtin.de.tr index 0552ef88b..3665a3f54 100644 --- a/builtin/locale/__builtin.de.tr +++ b/builtin/locale/__builtin.de.tr @@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Es wurden insgesamt @1 Datenpunkt(e) aufgeze The output is limited to '@1'.=Die Ausgabe ist beschränkt auf „@1“. Saving of profile failed: @1=Speichern des Profils fehlgeschlagen: @1 Profile saved to @1=Profil abgespeichert nach @1 +You died=Sie sind gestorben +Respawn=Wiederbeleben diff --git a/builtin/locale/__builtin.el.tr b/builtin/locale/__builtin.el.tr new file mode 100644 index 000000000..c14180a62 --- /dev/null +++ b/builtin/locale/__builtin.el.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Πέθανες +Respawn=Επανεμφάνηση diff --git a/builtin/locale/__builtin.eo.tr b/builtin/locale/__builtin.eo.tr index c3fec6c37..f1fe5333b 100644 --- a/builtin/locale/__builtin.eo.tr +++ b/builtin/locale/__builtin.eo.tr @@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Sume @1 ekzemplero(j) konserviĝis. The output is limited to '@1'.=La eligo estas limigita al «@1». Saving of profile failed: @1=Konservado de profilo malsukcesis: @1 Profile saved to @1=Profilo konservita al @1 +You died=Vi mortis +Respawn=Renaskiĝi diff --git a/builtin/locale/__builtin.es.tr b/builtin/locale/__builtin.es.tr new file mode 100644 index 000000000..3c4a66933 --- /dev/null +++ b/builtin/locale/__builtin.es.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Has muerto +Respawn=Reaparecer diff --git a/builtin/locale/__builtin.et.tr b/builtin/locale/__builtin.et.tr new file mode 100644 index 000000000..e0745c24a --- /dev/null +++ b/builtin/locale/__builtin.et.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Said surma +Respawn=Ärka ellu diff --git a/builtin/locale/__builtin.eu.tr b/builtin/locale/__builtin.eu.tr new file mode 100644 index 000000000..77f510855 --- /dev/null +++ b/builtin/locale/__builtin.eu.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Hil zara +Respawn=Birsortu diff --git a/builtin/locale/__builtin.fi.tr b/builtin/locale/__builtin.fi.tr new file mode 100644 index 000000000..f7c0ee038 --- /dev/null +++ b/builtin/locale/__builtin.fi.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Kuolit +Respawn=Synny uudelleen diff --git a/builtin/locale/__builtin.fil.tr b/builtin/locale/__builtin.fil.tr new file mode 100644 index 000000000..0755234a1 --- /dev/null +++ b/builtin/locale/__builtin.fil.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Namatay ka +Respawn=Mag-respawn diff --git a/builtin/locale/__builtin.fr.tr b/builtin/locale/__builtin.fr.tr index 6c3a244dc..d36574afd 100644 --- a/builtin/locale/__builtin.fr.tr +++ b/builtin/locale/__builtin.fr.tr @@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=@1 échantillons ont été collectés. The output is limited to '@1'.=La sortie est limitée à '@1'. Saving of profile failed: @1=La sauvegarde du profil a échoué : @1 Profile saved to @1=Le profil a été sauvegardé dans @1 +You died=Vous êtes mort +Respawn=Réapparaître diff --git a/builtin/locale/__builtin.ga.tr b/builtin/locale/__builtin.ga.tr new file mode 100644 index 000000000..e19959b10 --- /dev/null +++ b/builtin/locale/__builtin.ga.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Fuair tú bás +Respawn=Athsceith diff --git a/builtin/locale/__builtin.gl.tr b/builtin/locale/__builtin.gl.tr new file mode 100644 index 000000000..7f692f633 --- /dev/null +++ b/builtin/locale/__builtin.gl.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Morreches +Respawn=Reaparecer diff --git a/builtin/locale/__builtin.hu.tr b/builtin/locale/__builtin.hu.tr new file mode 100644 index 000000000..d7eebf7a0 --- /dev/null +++ b/builtin/locale/__builtin.hu.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Meghaltál +Respawn=Újraéledés diff --git a/builtin/locale/__builtin.id.tr b/builtin/locale/__builtin.id.tr index a541b0f8c..a7eba8c1c 100644 --- a/builtin/locale/__builtin.id.tr +++ b/builtin/locale/__builtin.id.tr @@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Total @1 sampel yang diambil. The output is limited to '@1'.=Keluaran dibatasi ke '@1'. Saving of profile failed: @1=Penyimpanan profil gagal: @1 Profile saved to @1=Profil disimpan ke @1 +You died=Anda mati +Respawn=Bangkit kembali diff --git a/builtin/locale/__builtin.it.tr b/builtin/locale/__builtin.it.tr index aecb80a9a..24cf38104 100644 --- a/builtin/locale/__builtin.it.tr +++ b/builtin/locale/__builtin.it.tr @@ -245,3 +245,5 @@ A total of @1 sample(s) were taken.=Son stati ottenuti campioni per un totale di The output is limited to '@1'.=L'output è limitato a '@1'. Saving of profile failed: @1=Errore nel salvare il profilo: @1 Profile saved to @1=Profilo salvato in @1 +You died=Sei morto +Respawn=Rinasci diff --git a/builtin/locale/__builtin.ja.tr b/builtin/locale/__builtin.ja.tr new file mode 100644 index 000000000..1138edddd --- /dev/null +++ b/builtin/locale/__builtin.ja.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=死んでしまった +Respawn=リスポーン diff --git a/builtin/locale/__builtin.jbo.tr b/builtin/locale/__builtin.jbo.tr new file mode 100644 index 000000000..fe492bcdb --- /dev/null +++ b/builtin/locale/__builtin.jbo.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=.i do morsi +Respawn=tolcanci diff --git a/builtin/locale/__builtin.jv.tr b/builtin/locale/__builtin.jv.tr new file mode 100644 index 000000000..282cc5476 --- /dev/null +++ b/builtin/locale/__builtin.jv.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Panjenengan pejah +Respawn=Bangkit Malilh diff --git a/builtin/locale/__builtin.ko.tr b/builtin/locale/__builtin.ko.tr new file mode 100644 index 000000000..0d5d35a90 --- /dev/null +++ b/builtin/locale/__builtin.ko.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=사망했습니다 +Respawn=리스폰 diff --git a/builtin/locale/__builtin.kv.tr b/builtin/locale/__builtin.kv.tr new file mode 100644 index 000000000..f985d2ac4 --- /dev/null +++ b/builtin/locale/__builtin.kv.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Кулінныд +Respawn=Ловзьыны diff --git a/builtin/locale/__builtin.ky.tr b/builtin/locale/__builtin.ky.tr new file mode 100644 index 000000000..9ad468aa0 --- /dev/null +++ b/builtin/locale/__builtin.ky.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Сиз өлдүңүз. +Respawn=Кайтадан жаралуу diff --git a/builtin/locale/__builtin.lt.tr b/builtin/locale/__builtin.lt.tr new file mode 100644 index 000000000..cc613162c --- /dev/null +++ b/builtin/locale/__builtin.lt.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Jūs numirėte +Respawn=Prisikelti diff --git a/builtin/locale/__builtin.lv.tr b/builtin/locale/__builtin.lv.tr new file mode 100644 index 000000000..1da1fe3d8 --- /dev/null +++ b/builtin/locale/__builtin.lv.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Jūs nomirāt +Respawn=Atdzīvoties diff --git a/builtin/locale/__builtin.lzh.tr b/builtin/locale/__builtin.lzh.tr new file mode 100644 index 000000000..1af6aba98 --- /dev/null +++ b/builtin/locale/__builtin.lzh.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=尔死矣 +Respawn=复生 diff --git a/builtin/locale/__builtin.mn.tr b/builtin/locale/__builtin.mn.tr new file mode 100644 index 000000000..dd9a3465b --- /dev/null +++ b/builtin/locale/__builtin.mn.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Та үхсэн +Respawn=Дахин төрөх diff --git a/builtin/locale/__builtin.mr.tr b/builtin/locale/__builtin.mr.tr new file mode 100644 index 000000000..e4fcfb9b1 --- /dev/null +++ b/builtin/locale/__builtin.mr.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=तू मेलास +Respawn=पुनर्जन्म diff --git a/builtin/locale/__builtin.ms.tr b/builtin/locale/__builtin.ms.tr index ebf794e97..65ac557bf 100644 --- a/builtin/locale/__builtin.ms.tr +++ b/builtin/locale/__builtin.ms.tr @@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Sebanyak @1 sampel telah diambil secara kese The output is limited to '@1'.=Output dihadkan kepada '@1'. Saving of profile failed: @1=Penyimpanan profil telah gagal: @1 Profile saved to @1=Profil telah disimpan ke @1 +You died=Anda telah meninggal +Respawn=Jelma semula diff --git a/builtin/locale/__builtin.nb.tr b/builtin/locale/__builtin.nb.tr new file mode 100644 index 000000000..b02a2d282 --- /dev/null +++ b/builtin/locale/__builtin.nb.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Du døde +Respawn=Gjenoppstå diff --git a/builtin/locale/__builtin.nl.tr b/builtin/locale/__builtin.nl.tr new file mode 100644 index 000000000..bd23c04b0 --- /dev/null +++ b/builtin/locale/__builtin.nl.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Je bent gestorven +Respawn=Herboren worden diff --git a/builtin/locale/__builtin.nn.tr b/builtin/locale/__builtin.nn.tr new file mode 100644 index 000000000..240191f73 --- /dev/null +++ b/builtin/locale/__builtin.nn.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Du døydde +Respawn=Kom opp att diff --git a/builtin/locale/__builtin.oc.tr b/builtin/locale/__builtin.oc.tr new file mode 100644 index 000000000..34b6577ad --- /dev/null +++ b/builtin/locale/__builtin.oc.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Setz mòrt·a +Respawn=Tornar diff --git a/builtin/locale/__builtin.pl.tr b/builtin/locale/__builtin.pl.tr new file mode 100644 index 000000000..ec0a9b7c1 --- /dev/null +++ b/builtin/locale/__builtin.pl.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Nie żyjesz +Respawn=Wróć do gry diff --git a/builtin/locale/__builtin.pt.tr b/builtin/locale/__builtin.pt.tr new file mode 100644 index 000000000..fbdb461e5 --- /dev/null +++ b/builtin/locale/__builtin.pt.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Você morreu +Respawn=Renascer diff --git a/builtin/locale/__builtin.pt_BR.tr b/builtin/locale/__builtin.pt_BR.tr index e12e9fae8..e48425bda 100644 --- a/builtin/locale/__builtin.pt_BR.tr +++ b/builtin/locale/__builtin.pt_BR.tr @@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Um total de @1 amostra(s) foi coletada. The output is limited to '@1'.=A saída é limitada a '@1'. Saving of profile failed: @1=Falha ao salvar o perfil: @1 Profile saved to @1=Perfil salvo em @1 +You died=Você morreu +Respawn=Reviver diff --git a/builtin/locale/__builtin.ro.tr b/builtin/locale/__builtin.ro.tr new file mode 100644 index 000000000..7ff5e3c38 --- /dev/null +++ b/builtin/locale/__builtin.ro.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Ai murit +Respawn=Reînviere diff --git a/builtin/locale/__builtin.ru.tr b/builtin/locale/__builtin.ru.tr index d43fbc589..863cdd638 100644 --- a/builtin/locale/__builtin.ru.tr +++ b/builtin/locale/__builtin.ru.tr @@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Всего было взято @1 образ The output is limited to '@1'.=Вывод ограничен значением '@1'. Saving of profile failed: @1=Не удалось сохранить данные профилирования: @1 Profile saved to @1=Данные профилирования сохранены в @1 +You died=Вы умерли +Respawn=Возродиться diff --git a/builtin/locale/__builtin.sk.tr b/builtin/locale/__builtin.sk.tr new file mode 100644 index 000000000..e9e93f6a3 --- /dev/null +++ b/builtin/locale/__builtin.sk.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Zomrel si +Respawn=Oživiť diff --git a/builtin/locale/__builtin.sl.tr b/builtin/locale/__builtin.sl.tr new file mode 100644 index 000000000..6dc77c3b2 --- /dev/null +++ b/builtin/locale/__builtin.sl.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Umrl si +Respawn=Ponovno oživi diff --git a/builtin/locale/__builtin.sr_Cyrl.tr b/builtin/locale/__builtin.sr_Cyrl.tr new file mode 100644 index 000000000..68551e77a --- /dev/null +++ b/builtin/locale/__builtin.sr_Cyrl.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Умро си +Respawn=Врати се у живот diff --git a/builtin/locale/__builtin.sr_Latn.tr b/builtin/locale/__builtin.sr_Latn.tr new file mode 100644 index 000000000..4adc496b4 --- /dev/null +++ b/builtin/locale/__builtin.sr_Latn.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Umro/la si. +Respawn=Vrati se u zivot diff --git a/builtin/locale/__builtin.sv.tr b/builtin/locale/__builtin.sv.tr new file mode 100644 index 000000000..115014506 --- /dev/null +++ b/builtin/locale/__builtin.sv.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Du dog +Respawn=Återuppstå diff --git a/builtin/locale/__builtin.sw.tr b/builtin/locale/__builtin.sw.tr new file mode 100644 index 000000000..92aa1f3e1 --- /dev/null +++ b/builtin/locale/__builtin.sw.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Umekufa. +Respawn=Respawn diff --git a/builtin/locale/__builtin.tok.tr b/builtin/locale/__builtin.tok.tr new file mode 100644 index 000000000..14d131d6a --- /dev/null +++ b/builtin/locale/__builtin.tok.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=sina moli +Respawn=o kama sin diff --git a/builtin/locale/__builtin.tr.tr b/builtin/locale/__builtin.tr.tr new file mode 100644 index 000000000..dafec5630 --- /dev/null +++ b/builtin/locale/__builtin.tr.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Öldün +Respawn=Yeniden Canlan diff --git a/builtin/locale/__builtin.tt.tr b/builtin/locale/__builtin.tt.tr new file mode 100644 index 000000000..a162cd9b7 --- /dev/null +++ b/builtin/locale/__builtin.tt.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Сез үлдегез +Respawn=Тергезелергә diff --git a/builtin/locale/__builtin.uk.tr b/builtin/locale/__builtin.uk.tr new file mode 100644 index 000000000..e82a10bde --- /dev/null +++ b/builtin/locale/__builtin.uk.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Ви загинули +Respawn=Відродитися diff --git a/builtin/locale/__builtin.vi.tr b/builtin/locale/__builtin.vi.tr new file mode 100644 index 000000000..53caac93c --- /dev/null +++ b/builtin/locale/__builtin.vi.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=Bạn đã bị chết +Respawn=Hồi sinh diff --git a/builtin/locale/__builtin.zh_CN.tr b/builtin/locale/__builtin.zh_CN.tr new file mode 100644 index 000000000..5b1077429 --- /dev/null +++ b/builtin/locale/__builtin.zh_CN.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=您已死亡 +Respawn=重生 diff --git a/builtin/locale/__builtin.zh_TW.tr b/builtin/locale/__builtin.zh_TW.tr new file mode 100644 index 000000000..5b1077429 --- /dev/null +++ b/builtin/locale/__builtin.zh_TW.tr @@ -0,0 +1,3 @@ +# textdomain: __builtin +You died=您已死亡 +Respawn=重生 diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index 08d0317ab..cd651f1b3 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -338,8 +338,6 @@ Call these functions only at load time! is checked to see if the command exists, but after the input is parsed. * Return `true` to mark the command as handled, which means that the default handlers will be prevented. -* `minetest.register_on_death(function())` - * Called when the local player dies * `minetest.register_on_hp_modification(function(hp))` * Called when server modified player's HP * `minetest.register_on_damage_taken(function(hp))` @@ -487,8 +485,6 @@ Call these functions only at load time! * Returns `false` if the client is already disconnecting otherwise returns `true`. * `minetest.get_server_info()` * Returns [server info](#server-info). -* `minetest.send_respawn()` - * Sends a respawn request to the server. ### Storage API * `minetest.get_mod_storage()`: diff --git a/doc/lua_api.md b/doc/lua_api.md index 3a5f7e128..66a83542e 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5869,6 +5869,7 @@ Call these functions only at load time! * `minetest.register_on_dieplayer(function(ObjectRef, reason))` * Called when a player dies * `reason`: a PlayerHPChangeReason table, see register_on_player_hpchange + * For customizing the death screen, see `minetest.show_death_screen`. * `minetest.register_on_respawnplayer(function(ObjectRef))` * Called when player is to be respawned * Called _before_ repositioning of player occurs @@ -6573,6 +6574,13 @@ Formspec * `"INV"`: something failed * `"CHG"`: has been changed * `"VAL"`: not changed +* `minetest.show_death_screen(player, reason)` + * Called when the death screen should be shown. + * `player` is an ObjectRef, `reason` is a PlayerHPChangeReason table or nil. + * By default, this shows a simple formspec with the option to respawn. + Respawning is done via `ObjectRef:respawn`. + * You can override this to show a custom death screen. + * For general death handling, use `minetest.register_on_dieplayer` instead. Item handling ------------- diff --git a/src/client/client.cpp b/src/client/client.cpp index b14ef7004..1a2f51db9 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1359,9 +1359,9 @@ void Client::sendDamage(u16 damage) Send(&pkt); } -void Client::sendRespawn() +void Client::sendRespawnLegacy() { - NetworkPacket pkt(TOSERVER_RESPAWN, 0); + NetworkPacket pkt(TOSERVER_RESPAWN_LEGACY, 0); Send(&pkt); } diff --git a/src/client/client.h b/src/client/client.h index adbe7a70d..f9f77ede4 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -194,7 +194,7 @@ public: void handleCommand_Breath(NetworkPacket* pkt); void handleCommand_MovePlayer(NetworkPacket* pkt); void handleCommand_MovePlayerRel(NetworkPacket* pkt); - void handleCommand_DeathScreen(NetworkPacket* pkt); + void handleCommand_DeathScreenLegacy(NetworkPacket* pkt); void handleCommand_AnnounceMedia(NetworkPacket* pkt); void handleCommand_Media(NetworkPacket* pkt); void handleCommand_NodeDef(NetworkPacket* pkt); @@ -249,7 +249,7 @@ public: void sendChangePassword(const std::string &oldpassword, const std::string &newpassword); void sendDamage(u16 damage); - void sendRespawn(); + void sendRespawnLegacy(); void sendReady(); void sendHaveMedia(const std::vector &tokens); void sendUpdateClientInfo(const ClientDynamicInfo &info); diff --git a/src/client/clientevent.h b/src/client/clientevent.h index acd375a4e..8c505786b 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -35,7 +35,7 @@ enum ClientEventType : u8 CE_NONE, CE_PLAYER_DAMAGE, CE_PLAYER_FORCE_MOVE, - CE_DEATHSCREEN, + CE_DEATHSCREEN_LEGACY, CE_SHOW_FORMSPEC, CE_SHOW_LOCAL_FORMSPEC, CE_SPAWN_PARTICLE, @@ -96,13 +96,6 @@ struct ClientEvent f32 yaw; } player_force_move; struct - { - bool set_camera_point_target; - f32 camera_point_target_x; - f32 camera_point_target_y; - f32 camera_point_target_z; - } deathscreen; - struct { std::string *formspec; std::string *formname; diff --git a/src/client/game.cpp b/src/client/game.cpp index ed593abba..6cbf54f5a 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -186,7 +186,7 @@ struct LocalFormspecHandler : public TextDest assert(m_client != nullptr); if (fields.find("quit") != fields.end()) - m_client->sendRespawn(); + m_client->sendRespawnLegacy(); return; } @@ -837,7 +837,7 @@ private: bool disable_camera_update = false; }; - void showDeathFormspec(); + void showDeathFormspecLegacy(); void showPauseMenu(); void pauseAnimation(); @@ -847,7 +847,7 @@ private: void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_HandleParticleEvent(ClientEvent *event, @@ -2854,7 +2854,7 @@ const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = { {&Game::handleClientEvent_None}, {&Game::handleClientEvent_PlayerDamage}, {&Game::handleClientEvent_PlayerForceMove}, - {&Game::handleClientEvent_Deathscreen}, + {&Game::handleClientEvent_DeathscreenLegacy}, {&Game::handleClientEvent_ShowFormSpec}, {&Game::handleClientEvent_ShowLocalFormSpec}, {&Game::handleClientEvent_HandleParticleEvent}, @@ -2910,20 +2910,9 @@ void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientati cam->camera_pitch = event->player_force_move.pitch; } -void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam) +void Game::handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrientation *cam) { - // If client scripting is enabled, deathscreen is handled by CSM code in - // builtin/client/init.lua - if (client->modsLoaded()) - client->getScript()->on_death(); - else - showDeathFormspec(); - - /* Handle visualization */ - LocalPlayer *player = client->getEnv().getLocalPlayer(); - runData.damage_flash = 0; - player->hurt_tilt_timer = 0; - player->hurt_tilt_strength = 0; + showDeathFormspecLegacy(); } void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam) @@ -4468,7 +4457,7 @@ void Game::readSettings() ****************************************************************************/ /****************************************************************************/ -void Game::showDeathFormspec() +void Game::showDeathFormspecLegacy() { static std::string formspec_str = std::string("formspec_version[1]") + diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index d426d3fe7..7d0840754 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -81,7 +81,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_MOVE_PLAYER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MovePlayer }, // 0x34 { "TOCLIENT_ACCESS_DENIED_LEGACY", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_AccessDenied }, // 0x35 { "TOCLIENT_FOV", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Fov }, // 0x36 - { "TOCLIENT_DEATHSCREEN", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeathScreen }, // 0x37 + { "TOCLIENT_DEATHSCREEN_LEGACY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeathScreenLegacy }, // 0x37 { "TOCLIENT_MEDIA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Media }, // 0x38 null_command_handler, { "TOCLIENT_NODEDEF", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_NodeDef }, // 0x3a @@ -198,7 +198,7 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] = { "TOSERVER_DAMAGE", 0, true }, // 0x35 null_command_factory, // 0x36 { "TOSERVER_PLAYERITEM", 0, true }, // 0x37 - { "TOSERVER_RESPAWN", 0, true }, // 0x38 + { "TOSERVER_RESPAWN_LEGACY", 0, true }, // 0x38 { "TOSERVER_INTERACT", 0, true }, // 0x39 { "TOSERVER_REMOVED_SOUNDS", 2, true }, // 0x3a { "TOSERVER_NODEMETA_FIELDS", 0, true }, // 0x3b diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 5b7c2f527..2716879f4 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -653,20 +653,10 @@ void Client::handleCommand_MovePlayerRel(NetworkPacket *pkt) player->addPosition(added_pos); } -void Client::handleCommand_DeathScreen(NetworkPacket* pkt) +void Client::handleCommand_DeathScreenLegacy(NetworkPacket* pkt) { - bool set_camera_point_target; - v3f camera_point_target; - - *pkt >> set_camera_point_target; - *pkt >> camera_point_target; - ClientEvent *event = new ClientEvent(); - event->type = CE_DEATHSCREEN; - event->deathscreen.set_camera_point_target = set_camera_point_target; - event->deathscreen.camera_point_target_x = camera_point_target.X; - event->deathscreen.camera_point_target_y = camera_point_target.Y; - event->deathscreen.camera_point_target_z = camera_point_target.Z; + event->type = CE_DEATHSCREEN_LEGACY; m_client_event_queue.push(event); } diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 85931c71b..cae2a3291 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -231,6 +231,12 @@ with this program; if not, write to the Free Software Foundation, Inc., Move default hotbar from client-side C++ to server-side builtin Lua Add shadow tint to Lighting packets Add shadow color to CloudParam packets + Move death screen to server and make it a regular formspec + The server no longer triggers the hardcoded client-side death + formspec, but the client still supports it for compatibility with + old servers. + Rename TOCLIENT_DEATHSCREEN to TOCLIENT_DEATHSCREEN_LEGACY + Rename TOSERVER_RESPAWN to TOSERVER_RESPAWN_LEGACY [scheduled bump for 5.10.0] */ @@ -391,10 +397,10 @@ enum ToClientCommand : u16 f32 transition_time */ - TOCLIENT_DEATHSCREEN = 0x37, + TOCLIENT_DEATHSCREEN_LEGACY = 0x37, /* - u8 bool set camera point target - v3f1000 camera point target (to point the death cause or whatever) + u8 bool unused + v3f1000 unused */ TOCLIENT_MEDIA = 0x38, @@ -1002,10 +1008,7 @@ enum ToServerCommand : u16 [2] u16 item */ - TOSERVER_RESPAWN = 0x38, - /* - u16 TOSERVER_RESPAWN - */ + TOSERVER_RESPAWN_LEGACY = 0x38, TOSERVER_INTERACT = 0x39, /* diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 1cb413492..4b87c3fbe 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -82,7 +82,7 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] = { "TOSERVER_DAMAGE", TOSERVER_STATE_INGAME, &Server::handleCommand_Damage }, // 0x35 null_command_handler, // 0x36 { "TOSERVER_PLAYERITEM", TOSERVER_STATE_INGAME, &Server::handleCommand_PlayerItem }, // 0x37 - { "TOSERVER_RESPAWN", TOSERVER_STATE_INGAME, &Server::handleCommand_Respawn }, // 0x38 + null_command_handler, // 0x38 { "TOSERVER_INTERACT", TOSERVER_STATE_INGAME, &Server::handleCommand_Interact }, // 0x39 { "TOSERVER_REMOVED_SOUNDS", TOSERVER_STATE_INGAME, &Server::handleCommand_RemovedSounds }, // 0x3a { "TOSERVER_NODEMETA_FIELDS", TOSERVER_STATE_INGAME, &Server::handleCommand_NodeMetaFields }, // 0x3b @@ -181,7 +181,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_MOVE_PLAYER", 0, true }, // 0x34 null_command_factory, // 0x35 { "TOCLIENT_FOV", 0, true }, // 0x36 - { "TOCLIENT_DEATHSCREEN", 0, true }, // 0x37 + null_command_factory, // 0x37 { "TOCLIENT_MEDIA", 2, true }, // 0x38 null_command_factory, // 0x39 { "TOCLIENT_NODEDEF", 0, true }, // 0x3A diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 7d8555c8e..c17d32e41 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -858,33 +858,6 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) playersao->getPlayer()->setWieldIndex(item); } -void Server::handleCommand_Respawn(NetworkPacket* pkt) -{ - session_t peer_id = pkt->getPeerId(); - RemotePlayer *player = m_env->getPlayer(peer_id); - if (player == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; - DisconnectPeer(peer_id); - return; - } - - PlayerSAO *playersao = player->getPlayerSAO(); - assert(playersao); - - if (!playersao->isDead()) - return; - - RespawnPlayer(peer_id); - - actionstream << player->getName() << " respawns at " - << (playersao->getBasePosition() / BS) << std::endl; - - // ActiveObject is added to environment in AsyncRunStep after - // the previous addition has been successfully removed -} - bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what) { ItemStack selected_item, hand_item; diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 6faa0695c..78d0ec44d 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -125,21 +125,6 @@ void ScriptApiClient::on_hp_modification(int32_t newhp) } } -void ScriptApiClient::on_death() -{ - SCRIPTAPI_PRECHECKHEADER - - // Get registered shutdown hooks - lua_getglobal(L, "core"); - lua_getfield(L, -1, "registered_on_death"); - // Call callbacks - try { - runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); - } catch (LuaError &e) { - getClient()->setFatalError(e); - } -} - void ScriptApiClient::environment_step(float dtime) { SCRIPTAPI_PRECHECKHEADER diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 8a5523d0d..74cc0d898 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -49,7 +49,6 @@ public: void on_damage_taken(int32_t damage_amount); void on_hp_modification(int32_t newhp); - void on_death(); void environment_step(float dtime); void on_formspec_input(const std::string &formname, const StringMap &fields); diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index da19ed0ea..3bd9fc04d 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -157,13 +157,6 @@ int ModApiClient::l_show_formspec(lua_State *L) return 1; } -// send_respawn() -int ModApiClient::l_send_respawn(lua_State *L) -{ - getClient(L)->sendRespawn(); - return 0; -} - // disconnect() int ModApiClient::l_disconnect(lua_State *L) { @@ -348,7 +341,6 @@ void ModApiClient::Initialize(lua_State *L, int top) API_FCT(clear_out_chat_queue); API_FCT(get_player_names); API_FCT(show_formspec); - API_FCT(send_respawn); API_FCT(gettext); API_FCT(get_node_or_nil); API_FCT(disconnect); diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index e960dc4cf..d726bc8a3 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -51,9 +51,6 @@ private: // show_formspec(name, formspec) static int l_show_formspec(lua_State *L); - // send_respawn() - static int l_send_respawn(lua_State *L); - // disconnect() static int l_disconnect(lua_State *L); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index d6d608aab..ebd1bde71 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2693,11 +2693,11 @@ int ObjectRef::l_respawn(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkObject(L, 1); - RemotePlayer *player = getplayer(ref); - if (player == nullptr) + auto *psao = getplayersao(ref); + if (psao == nullptr) return 0; - getServer(L)->RespawnPlayer(player->getPeerId()); + psao->respawn(); lua_pushboolean(L, true); return 1; } diff --git a/src/server.cpp b/src/server.cpp index 4be3aa26a..fe3dc8516 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1159,10 +1159,6 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) // Send HP SendPlayerHP(playersao, false); - // Send death screen - if (playersao->isDead()) - SendDeathscreen(peer_id, false, v3f(0,0,0)); - // Send Breath SendPlayerBreath(playersao); @@ -1405,14 +1401,6 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason, Send(&pkt); } -void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target, - v3f camera_point_target) -{ - NetworkPacket pkt(TOCLIENT_DEATHSCREEN, 1 + sizeof(v3f), peer_id); - pkt << set_camera_point_target << camera_point_target; - Send(&pkt); -} - void Server::SendItemDef(session_t peer_id, IItemDefManager *itemdef, u16 protocol_version) { @@ -2803,32 +2791,8 @@ void Server::HandlePlayerDeath(PlayerSAO *playersao, const PlayerHPChangeReason // Trigger scripted stuff m_script->on_dieplayer(playersao, reason); - - SendDeathscreen(playersao->getPeerID(), false, v3f(0,0,0)); } -void Server::RespawnPlayer(session_t peer_id) -{ - PlayerSAO *playersao = getPlayerSAO(peer_id); - assert(playersao); - - infostream << "Server::RespawnPlayer(): Player " - << playersao->getPlayer()->getName() - << " respawns" << std::endl; - - const auto *prop = playersao->accessObjectProperties(); - playersao->setHP(prop->hp_max, - PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN)); - playersao->setBreath(prop->breath_max); - - bool repositioned = m_script->on_respawnplayer(playersao); - if (!repositioned) { - // setPos will send the new position to client - playersao->setPos(findSpawnPos()); - } -} - - void Server::DenySudoAccess(session_t peer_id) { NetworkPacket pkt(TOCLIENT_DENY_SUDO_MODE, 0, peer_id); diff --git a/src/server.h b/src/server.h index 58805c667..5f6086cde 100644 --- a/src/server.h +++ b/src/server.h @@ -193,7 +193,6 @@ public: void handleCommand_ChatMessage(NetworkPacket* pkt); void handleCommand_Damage(NetworkPacket* pkt); void handleCommand_PlayerItem(NetworkPacket* pkt); - void handleCommand_Respawn(NetworkPacket* pkt); void handleCommand_Interact(NetworkPacket* pkt); void handleCommand_RemovedSounds(NetworkPacket* pkt); void handleCommand_NodeMetaFields(NetworkPacket* pkt); @@ -356,8 +355,6 @@ public: void setLighting(RemotePlayer *player, const Lighting &lighting); - void RespawnPlayer(session_t peer_id); - /* con::PeerHandler implementation. */ void peerAdded(con::IPeer *peer); void deletingPeer(con::IPeer *peer, bool timeout); @@ -486,8 +483,6 @@ private: void SendBreath(session_t peer_id, u16 breath); void SendAccessDenied(session_t peer_id, AccessDeniedCode reason, std::string_view custom_reason, bool reconnect = false); - void SendDeathscreen(session_t peer_id, bool set_camera_point_target, - v3f camera_point_target); void SendItemDef(session_t peer_id, IItemDefManager *itemdef, u16 protocol_version); void SendNodeDef(session_t peer_id, const NodeDefManager *nodedef, u16 protocol_version); diff --git a/src/server/clientiface.h b/src/server/clientiface.h index f930e7f3e..3f5ba6434 100644 --- a/src/server/clientiface.h +++ b/src/server/clientiface.h @@ -126,7 +126,7 @@ class EmergeManager; | TOCLIENT_INVENTORY | | | | | TOCLIENT_HP (opt) | \-----------------/ | | TOCLIENT_BREATH | | -| TOCLIENT_DEATHSCREEN | | +| TOCLIENT_DEATHSCREEN_LEGACY | | +-----------------------------+ | | | v | diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 11922b2c6..30c41bb1e 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -558,6 +558,21 @@ void PlayerSAO::setBreath(const u16 breath, bool send) m_env->getGameDef()->SendPlayerBreath(this); } +void PlayerSAO::respawn() +{ + infostream << "PlayerSAO::respawn(): Player " << m_player->getName() + << " respawns" << std::endl; + + setHP(m_prop.hp_max, PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN)); + setBreath(m_prop.breath_max); + + bool repositioned = m_env->getScriptIface()->on_respawnplayer(this); + if (!repositioned) { + // setPos will send the new position to client + setPos(m_env->getGameDef()->findSpawnPos()); + } +} + Inventory *PlayerSAO::getInventory() const { return m_player ? &m_player->inventory : nullptr; diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 95bd1d109..487e37957 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -124,6 +124,7 @@ public: void setHPRaw(u16 hp) { m_hp = hp; } u16 getBreath() const { return m_breath; } void setBreath(const u16 breath, bool send = true); + void respawn(); /* Inventory interface From 588a0f83e9dffd86b612445a1494e205b5d78a2e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 11 Sep 2024 19:17:08 +0200 Subject: [PATCH 134/200] Divorce map database locking from env lock (#15151) --- src/emerge.cpp | 68 ++++++++++++++++--- src/emerge.h | 8 +++ src/emerge_internal.h | 22 ++++-- src/main.cpp | 3 +- src/servermap.cpp | 152 ++++++++++++++++++++++++------------------ src/servermap.h | 34 ++++++++-- 6 files changed, 197 insertions(+), 90 deletions(-) diff --git a/src/emerge.cpp b/src/emerge.cpp index 425e294b8..f66b78909 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "log.h" #include "servermap.h" +#include "database/database.h" #include "mapblock.h" #include "mapgen/mg_biome.h" #include "mapgen/mg_ore.h" @@ -185,10 +186,22 @@ SchematicManager *EmergeManager::getWritableSchematicManager() return schemmgr; } +void EmergeManager::initMap(MapDatabaseAccessor *holder) +{ + FATAL_ERROR_IF(m_db, "Map database already initialized."); + assert(holder->dbase); + m_db = holder; +} + +void EmergeManager::resetMap() +{ + FATAL_ERROR_IF(m_threads_active, "Threads are still active."); + m_db = nullptr; +} void EmergeManager::initMapgens(MapgenParams *params) { - FATAL_ERROR_IF(!m_mapgens.empty(), "Mapgen already initialised."); + FATAL_ERROR_IF(!m_mapgens.empty(), "Mapgen already initialized."); mgparams = params; @@ -466,7 +479,7 @@ void EmergeThread::signal() } -bool EmergeThread::pushBlock(const v3s16 &pos) +bool EmergeThread::pushBlock(v3s16 pos) { m_block_queue.push(pos); return true; @@ -491,7 +504,7 @@ void EmergeThread::cancelPendingItems() } -void EmergeThread::runCompletionCallbacks(const v3s16 &pos, EmergeAction action, +void EmergeThread::runCompletionCallbacks(v3s16 pos, EmergeAction action, const EmergeCallbackList &callbacks) { m_emerge->reportCompletedEmerge(action); @@ -524,21 +537,36 @@ bool EmergeThread::popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata) } -EmergeAction EmergeThread::getBlockOrStartGen( - const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *bmdata) +EmergeAction EmergeThread::getBlockOrStartGen(const v3s16 pos, bool allow_gen, + const std::string *from_db, MapBlock **block, BlockMakeData *bmdata) { MutexAutoLock envlock(m_server->m_env_mutex); + auto block_ok = [] (MapBlock *b) { + return b && b->isGenerated(); + }; + // 1). Attempt to fetch block from memory *block = m_map->getBlockNoCreateNoEx(pos); if (*block) { - if ((*block)->isGenerated()) + if (block_ok(*block)) { + // if we just read it from the db but the block exists that means + // someone else was faster. don't touch it to prevent data loss. + if (from_db) + verbosestream << "getBlockOrStartGen: block loading raced" << std::endl; return EMERGE_FROM_MEMORY; + } } else { - // 2). Attempt to load block from disk if it was not in the memory - *block = m_map->loadBlock(pos); - if (*block && (*block)->isGenerated()) + if (!from_db) { + // 2). We should attempt loading it return EMERGE_FROM_DISK; + } + // 2). Second invocation, we have the data + if (!from_db->empty()) { + *block = m_map->loadBlock(*from_db, pos); + if (block_ok(*block)) + return EMERGE_FROM_DISK; + } } // 3). Attempt to start generation @@ -643,7 +671,8 @@ void *EmergeThread::run() BEGIN_DEBUG_EXCEPTION_HANDLER v3s16 pos; - std::map modified_blocks; + std::map modified_blocks; + std::string databuf; m_map = &m_server->m_env->getServerMap(); m_emerge = m_server->getEmergeManager(); @@ -669,13 +698,30 @@ void *EmergeThread::run() continue; } + g_profiler->add(m_name + ": processed [#]", 1); + if (blockpos_over_max_limit(pos)) continue; bool allow_gen = bedata.flags & BLOCK_EMERGE_ALLOW_GEN; EMERGE_DBG_OUT("pos=" << pos << " allow_gen=" << allow_gen); - action = getBlockOrStartGen(pos, allow_gen, &block, &bmdata); + action = getBlockOrStartGen(pos, allow_gen, nullptr, &block, &bmdata); + + /* Try to load it */ + if (action == EMERGE_FROM_DISK) { + auto &m_db = *m_emerge->m_db; + { + ScopeProfiler sp(g_profiler, "EmergeThread: load block - async (sum)"); + MutexAutoLock dblock(m_db.mutex); + m_db.loadBlock(pos, databuf); + } + // actually load it, then decide again + action = getBlockOrStartGen(pos, allow_gen, &databuf, &block, &bmdata); + databuf.clear(); + } + + /* Generate it */ if (action == EMERGE_GENERATED) { bool error = false; m_trans_liquid = &bmdata.transforming_liquid; diff --git a/src/emerge.h b/src/emerge.h index d7f018feb..4e0f738d8 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -46,6 +46,7 @@ class DecorationManager; class SchematicManager; class Server; class ModApiMapgen; +struct MapDatabaseAccessor; // Structure containing inputs/outputs for chunk generation struct BlockMakeData { @@ -173,6 +174,10 @@ public: SchematicManager *getWritableSchematicManager(); void initMapgens(MapgenParams *mgparams); + /// @param holder non-owned reference that must stay alive + void initMap(MapDatabaseAccessor *holder); + /// resets the reference + void resetMap(); void startThreads(); void stopThreads(); @@ -206,6 +211,9 @@ private: std::vector m_threads; bool m_threads_active = false; + // The map database + MapDatabaseAccessor *m_db = nullptr; + std::mutex m_queue_mutex; std::map m_blocks_enqueued; std::unordered_map m_peer_queue_count; diff --git a/src/emerge_internal.h b/src/emerge_internal.h index 439c8227b..08e36778d 100644 --- a/src/emerge_internal.h +++ b/src/emerge_internal.h @@ -40,7 +40,7 @@ class EmergeScripting; class EmergeThread : public Thread { public: bool enable_mapgen_debug_info; - int id; + const int id; // Index of this thread EmergeThread(Server *server, int ethreadid); ~EmergeThread() = default; @@ -49,7 +49,7 @@ public: void signal(); // Requires queue mutex held - bool pushBlock(const v3s16 &pos); + bool pushBlock(v3s16 pos); void cancelPendingItems(); @@ -59,7 +59,7 @@ public: protected: void runCompletionCallbacks( - const v3s16 &pos, EmergeAction action, + v3s16 pos, EmergeAction action, const EmergeCallbackList &callbacks); private: @@ -79,8 +79,20 @@ private: bool popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata); - EmergeAction getBlockOrStartGen( - const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *data); + /** + * Try to get a block from memory and decide what to do. + * + * @param pos block position + * @param from_db serialized block data, optional + * (for second call after EMERGE_FROM_DISK was returned) + * @param allow_gen allow invoking mapgen? + * @param block output pointer for block + * @param data info for mapgen + * @return what to do for this block + */ + EmergeAction getBlockOrStartGen(v3s16 pos, bool allow_gen, + const std::string *from_db, MapBlock **block, BlockMakeData *data); + MapBlock *finishGen(v3s16 pos, BlockMakeData *bmdata, std::map *modified_blocks); diff --git a/src/main.cpp b/src/main.cpp index 30db81aa9..9f737b86d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1278,8 +1278,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting { MapBlock mb(v3s16(0,0,0), &server); - u8 ver = readU8(iss); - mb.deSerialize(iss, ver, true); + ServerMap::deSerializeBlock(&mb, iss); oss.str(""); oss.clear(); diff --git a/src/servermap.cpp b/src/servermap.cpp index 0248497c1..f57e5b5e4 100644 --- a/src/servermap.cpp +++ b/src/servermap.cpp @@ -51,6 +51,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database/database-postgresql.h" #endif +/* + Helpers +*/ + +void MapDatabaseAccessor::loadBlock(v3s16 blockpos, std::string &ret) +{ + ret.clear(); + dbase->loadBlock(blockpos, &ret); + if (ret.empty() && dbase_ro) + dbase_ro->loadBlock(blockpos, &ret); +} + /* ServerMap */ @@ -67,7 +79,7 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, emerge->map_settings_mgr = &settings_mgr; /* - Try to load map; if not found, create a new one. + Try to open map; if not found, create a new one. */ // Determine which database backend to use @@ -79,10 +91,10 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, conf.set("backend", "sqlite3"); } std::string backend = conf.get("backend"); - dbase = createDatabase(backend, savedir, conf); + m_db.dbase = createDatabase(backend, savedir, conf); if (conf.exists("readonly_backend")) { std::string readonly_dir = savedir + DIR_DELIM + "readonly"; - dbase_ro = createDatabase(conf.get("readonly_backend"), readonly_dir, conf); + m_db.dbase_ro = createDatabase(conf.get("readonly_backend"), readonly_dir, conf); } if (!conf.updateConfigFile(conf_path.c_str())) errorstream << "ServerMap::ServerMap(): Failed to update world.mt!" << std::endl; @@ -90,6 +102,9 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, m_savedir = savedir; m_map_saving_enabled = false; + // Inform EmergeManager of db handles + m_emerge->initMap(&m_db); + m_save_time_counter = mb->addCounter( "minetest_map_save_time", "Time spent saving blocks (in microseconds)"); m_save_count_counter = mb->addCounter( @@ -159,11 +174,15 @@ ServerMap::~ServerMap() << ", exception: " << e.what() << std::endl; } - /* - Close database if it was opened - */ - delete dbase; - delete dbase_ro; + m_emerge->resetMap(); + + { + MutexAutoLock dblock(m_db.mutex); + delete m_db.dbase; + m_db.dbase = nullptr; + delete m_db.dbase_ro; + m_db.dbase_ro = nullptr; + } deleteDetachedBlocks(); } @@ -547,9 +566,10 @@ void ServerMap::save(ModifiedState save_level) void ServerMap::listAllLoadableBlocks(std::vector &dst) { - dbase->listAllLoadableBlocks(dst); - if (dbase_ro) - dbase_ro->listAllLoadableBlocks(dst); + MutexAutoLock dblock(m_db.mutex); + m_db.dbase->listAllLoadableBlocks(dst); + if (m_db.dbase_ro) + m_db.dbase_ro->listAllLoadableBlocks(dst); } void ServerMap::listAllLoadedBlocks(std::vector &dst) @@ -597,17 +617,21 @@ MapDatabase *ServerMap::createDatabase( void ServerMap::beginSave() { - dbase->beginSave(); + MutexAutoLock dblock(m_db.mutex); + m_db.dbase->beginSave(); } void ServerMap::endSave() { - dbase->endSave(); + MutexAutoLock dblock(m_db.mutex); + m_db.dbase->endSave(); } bool ServerMap::saveBlock(MapBlock *block) { - return saveBlock(block, dbase, m_map_compression_level); + // FIXME: serialization happens under mutex + MutexAutoLock dblock(m_db.mutex); + return saveBlock(block, m_db.dbase, m_map_compression_level); } bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db, int compression_level) @@ -634,18 +658,27 @@ bool ServerMap::saveBlock(MapBlock *block, MapDatabase *db, int compression_leve return ret; } -void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load) +void ServerMap::deSerializeBlock(MapBlock *block, std::istream &is) { + ScopeProfiler sp(g_profiler, "ServerMap: deSer block", SPT_AVG, PRECISION_MICRO); + + u8 version = readU8(is); + if (is.fail()) + throw SerializationError("Failed to read MapBlock version"); + + block->deSerialize(is, version, true); +} + +MapBlock *ServerMap::loadBlock(const std::string &blob, v3s16 p3d, bool save_after_load) +{ + ScopeProfiler sp(g_profiler, "ServerMap: load block", SPT_AVG, PRECISION_MICRO); + MapBlock *block = nullptr; + bool created_new = false; + try { - std::istringstream is(*blob, std::ios_base::binary); + v2s16 p2d(p3d.X, p3d.Z); + MapSector *sector = createSector(p2d); - u8 version = readU8(is); - - if(is.fail()) - throw SerializationError("ServerMap::loadBlock(): Failed" - " to read MapBlock version"); - - MapBlock *block = nullptr; std::unique_ptr block_created_new; block = sector->getBlockNoCreateNoEx(p3d.Y); if (!block) { @@ -654,31 +687,16 @@ void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool } { - ScopeProfiler sp(g_profiler, "ServerMap: deSer block", SPT_AVG, PRECISION_MICRO); - block->deSerialize(is, version, true); + std::istringstream iss(blob, std::ios_base::binary); + deSerializeBlock(block, iss); } // If it's a new block, insert it to the map if (block_created_new) { sector->insertBlock(std::move(block_created_new)); - ReflowScan scanner(this, m_emerge->ndef); - scanner.scan(block, &m_transforming_liquid); + created_new = true; } - - /* - Save blocks loaded in old format in new format - */ - - //if(version < SER_FMT_VER_HIGHEST_READ || save_after_load) - // Only save if asked to; no need to update version - if(save_after_load) - saveBlock(block); - - // We just loaded it from, so it's up-to-date. - block->resetModified(); - } - catch(SerializationError &e) - { + } catch (SerializationError &e) { errorstream<<"Invalid block data in database" <<" ("<ndef); + scanner.scan(block, &m_transforming_liquid); - std::string ret; - dbase->loadBlock(blockpos, &ret); - if (!ret.empty()) { - loadBlock(&ret, blockpos, createSector(p2d), false); - } else if (dbase_ro) { - dbase_ro->loadBlock(blockpos, &ret); - if (!ret.empty()) { - loadBlock(&ret, blockpos, createSector(p2d), false); - } - } else { - return NULL; - } - - MapBlock *block = getBlockNoCreateNoEx(blockpos); - if (created_new && (block != NULL)) { std::map modified_blocks; // Fix lighting if necessary voxalgo::update_block_border_lighting(this, block, modified_blocks); if (!modified_blocks.empty()) { - //Modified lighting, send event MapEditEvent event; event.type = MEET_OTHER; event.setModifiedBlocks(modified_blocks); dispatchEvent(event); } } + + if (save_after_load) + saveBlock(block); + + // We just loaded it, so it's up-to-date. + block->resetModified(); + return block; } +MapBlock* ServerMap::loadBlock(v3s16 blockpos) +{ + std::string data; + { + ScopeProfiler sp(g_profiler, "ServerMap: load block - sync (sum)"); + MutexAutoLock dblock(m_db.mutex); + m_db.loadBlock(blockpos, data); + } + + if (!data.empty()) + return loadBlock(data, blockpos); + return getBlockNoCreateNoEx(blockpos); +} + bool ServerMap::deleteBlock(v3s16 blockpos) { - if (!dbase->deleteBlock(blockpos)) + MutexAutoLock dblock(m_db.mutex); + if (!m_db.dbase->deleteBlock(blockpos)) return false; MapBlock *block = getBlockNoCreateNoEx(blockpos); diff --git a/src/servermap.h b/src/servermap.h index 7a8a84b9b..3a2102668 100644 --- a/src/servermap.h +++ b/src/servermap.h @@ -33,9 +33,22 @@ class IRollbackManager; class EmergeManager; class ServerEnvironment; struct BlockMakeData; - class MetricsBackend; +// TODO: this could wrap all calls to MapDatabase, including locking +struct MapDatabaseAccessor { + /// Lock, to be taken for any operation + std::mutex mutex; + /// Main database + MapDatabase *dbase = nullptr; + /// Fallback database for read operations + MapDatabase *dbase_ro = nullptr; + + /// Load a block, taking dbase_ro into account. + /// @note call locked + void loadBlock(v3s16 blockpos, std::string &ret); +}; + /* ServerMap @@ -75,7 +88,7 @@ public: MapBlock *createBlock(v3s16 p); /* - Forcefully get a block from somewhere. + Forcefully get a block from somewhere (blocking!). - Memory - Load from disk - Create blank filled with CONTENT_IGNORE @@ -114,9 +127,16 @@ public: bool saveBlock(MapBlock *block) override; static bool saveBlock(MapBlock *block, MapDatabase *db, int compression_level = -1); - MapBlock* loadBlock(v3s16 p); - // Database version - void loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool save_after_load=false); + + // Load block in a synchronous fashion + MapBlock *loadBlock(v3s16 p); + /// Load a block that was already read from disk. Used by EmergeManager. + /// @return non-null block (but can be blank) + MapBlock *loadBlock(const std::string &blob, v3s16 p, bool save_after_load=false); + + // Helper for deserializing blocks from disk + // @throws SerializationError + static void deSerializeBlock(MapBlock *block, std::istream &is); // Blocks are removed from the map but not deleted from memory until // deleteDetachedBlocks() is called, since pointers to them may still exist @@ -185,8 +205,8 @@ private: This is reset to false when written on disk. */ bool m_map_metadata_changed = true; - MapDatabase *dbase = nullptr; - MapDatabase *dbase_ro = nullptr; + + MapDatabaseAccessor m_db; // Map metrics MetricGaugePtr m_loaded_blocks_gauge; From 0220d0d4928f7473ea4bbe6b7537cd45ae877ee5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 15 Sep 2024 21:25:03 +0200 Subject: [PATCH 135/200] Encapsulate envlock --- src/emerge.cpp | 6 +++--- src/script/lua_api/l_env.cpp | 2 +- src/server.cpp | 31 +++++++++++++++---------------- src/server.h | 14 ++++++++++++-- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/emerge.cpp b/src/emerge.cpp index f66b78909..2d0f67505 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -540,7 +540,7 @@ bool EmergeThread::popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata) EmergeAction EmergeThread::getBlockOrStartGen(const v3s16 pos, bool allow_gen, const std::string *from_db, MapBlock **block, BlockMakeData *bmdata) { - MutexAutoLock envlock(m_server->m_env_mutex); + Server::EnvAutoLock envlock(m_server); auto block_ok = [] (MapBlock *b) { return b && b->isGenerated(); @@ -581,7 +581,7 @@ EmergeAction EmergeThread::getBlockOrStartGen(const v3s16 pos, bool allow_gen, MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, std::map *modified_blocks) { - MutexAutoLock envlock(m_server->m_env_mutex); + Server::EnvAutoLock envlock(m_server); ScopeProfiler sp(g_profiler, "EmergeThread: after Mapgen::makeChunk", SPT_AVG); @@ -762,7 +762,7 @@ void *EmergeThread::run() MapEditEvent event; event.type = MEET_OTHER; event.setModifiedBlocks(modified_blocks); - MutexAutoLock envlock(m_server->m_env_mutex); + Server::EnvAutoLock envlock(m_server); m_map->dispatchEvent(event); } modified_blocks.clear(); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 125a352bc..726300b07 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -160,7 +160,7 @@ void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param) // state must be protected by envlock Server *server = state->script->getServer(); - MutexAutoLock envlock(server->m_env_mutex); + Server::EnvAutoLock envlock(server); state->refcount--; diff --git a/src/server.cpp b/src/server.cpp index fe3dc8516..8a45d7369 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -353,7 +353,7 @@ Server::~Server() m_emerge->stopThreads(); if (m_env) { - MutexAutoLock envlock(m_env_mutex); + EnvAutoLock envlock(this); infostream << "Server: Executing shutdown hooks" << std::endl; try { @@ -461,7 +461,7 @@ void Server::init() } //lock environment - MutexAutoLock envlock(m_env_mutex); + EnvAutoLock envlock(this); // Create the Map (loads map_meta.txt, overriding configured mapgen params) auto startup_server_map = std::make_unique(m_path_world, this, @@ -653,7 +653,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step) } { - MutexAutoLock lock(m_env_mutex); + EnvAutoLock lock(this); float max_lag = m_env->getMaxLagEstimate(); constexpr float lag_warn_threshold = 2.0f; @@ -686,7 +686,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step) static const float map_timer_and_unload_dtime = 2.92; if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { - MutexAutoLock lock(m_env_mutex); + EnvAutoLock lock(this); // Run Map's timers and unload unused data ScopeProfiler sp(g_profiler, "Server: map timer and unload"); m_env->getMap().timerUpdate(map_timer_and_unload_dtime, @@ -704,7 +704,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step) */ if (m_admin_chat) { if (!m_admin_chat->command_queue.empty()) { - MutexAutoLock lock(m_env_mutex); + EnvAutoLock lock(this); while (!m_admin_chat->command_queue.empty()) { ChatEvent *evt = m_admin_chat->command_queue.pop_frontNoEx(); handleChatInterfaceEvent(evt); @@ -725,7 +725,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step) { m_liquid_transform_timer -= m_liquid_transform_every; - MutexAutoLock lock(m_env_mutex); + EnvAutoLock lock(this); ScopeProfiler sp(g_profiler, "Server: liquid transform"); @@ -786,7 +786,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step) */ { //infostream<<"Server: Checking added and deleted active objects"<getFloat("server_map_save_interval"); if (counter >= save_interval) { counter = 0.0; - MutexAutoLock lock(m_env_mutex); + EnvAutoLock lock(this); ScopeProfiler sp(g_profiler, "Server: map saving (sum)"); @@ -1191,7 +1191,7 @@ inline void Server::handleCommand(NetworkPacket *pkt) void Server::ProcessData(NetworkPacket *pkt) { // Environment is locked first. - MutexAutoLock envlock(m_env_mutex); + EnvAutoLock envlock(this); ScopeProfiler sp(g_profiler, "Server: Process network packet (sum)"); u32 peer_id = pkt->getPeerId(); @@ -2363,8 +2363,7 @@ void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, void Server::SendBlocks(float dtime) { - MutexAutoLock envlock(m_env_mutex); - //TODO check if one big lock could be faster then multiple small ones + EnvAutoLock envlock(this); std::vector queue; @@ -2695,7 +2694,7 @@ void Server::sendRequestedMedia(session_t peer_id, void Server::stepPendingDynMediaCallbacks(float dtime) { - MutexAutoLock lock(m_env_mutex); + EnvAutoLock lock(this); for (auto it = m_pending_dyn_media.begin(); it != m_pending_dyn_media.end();) { it->second.expiry_timer -= dtime; @@ -2914,7 +2913,7 @@ void Server::DeleteClient(session_t peer_id, ClientDeletionReason reason) } } { - MutexAutoLock env_lock(m_env_mutex); + EnvAutoLock envlock(this); m_clients.DeleteClient(peer_id); } } @@ -4107,7 +4106,7 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code) std::unordered_map Server::getMediaList() { - MutexAutoLock env_lock(m_env_mutex); + EnvAutoLock envlock(this); std::unordered_map ret; for (auto &it : m_media) { diff --git a/src/server.h b/src/server.h index 5f6086cde..51d52d443 100644 --- a/src/server.h +++ b/src/server.h @@ -424,8 +424,14 @@ public: // Bind address Address m_bind_addr; - // Environment mutex (envlock) - std::mutex m_env_mutex; + // Public helper for taking the envlock in a scope + class EnvAutoLock { + public: + EnvAutoLock(Server *server): m_lock(server->m_env_mutex) {} + + private: + MutexAutoLock m_lock; + }; protected: /* Do not add more members here, this is only required to make unit tests work. */ @@ -600,6 +606,10 @@ private: /* Variables */ + + // Environment mutex (envlock) + std::mutex m_env_mutex; + // World directory std::string m_path_world; std::string m_path_mod_data; From 5f308deb50133e4d42ec2920cd98c1d3797fa58f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 15 Sep 2024 22:16:05 +0200 Subject: [PATCH 136/200] Switch env lock to fair mutex implementation --- src/server.h | 5 ++-- src/threading/ordered_mutex.h | 46 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/threading/ordered_mutex.h diff --git a/src/server.h b/src/server.h index 51d52d443..6b48d929d 100644 --- a/src/server.h +++ b/src/server.h @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/metricsbackend.h" #include "serverenvironment.h" #include "server/clientiface.h" +#include "threading/ordered_mutex.h" #include "chatmessage.h" #include "sound.h" #include "translation.h" @@ -430,7 +431,7 @@ public: EnvAutoLock(Server *server): m_lock(server->m_env_mutex) {} private: - MutexAutoLock m_lock; + std::lock_guard m_lock; }; protected: @@ -608,7 +609,7 @@ private: */ // Environment mutex (envlock) - std::mutex m_env_mutex; + ordered_mutex m_env_mutex; // World directory std::string m_path_world; diff --git a/src/threading/ordered_mutex.h b/src/threading/ordered_mutex.h new file mode 100644 index 000000000..f7fb4d309 --- /dev/null +++ b/src/threading/ordered_mutex.h @@ -0,0 +1,46 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include + +/* + Fair mutex based on ticketing approach. + Satisfies `Mutex` C++11 requirements. +*/ +class ordered_mutex { +public: + ordered_mutex() : next_ticket(0), counter(0) {} + + void lock() + { + std::unique_lock autolock(cv_lock); + const auto ticket = next_ticket++; + cv.wait(autolock, [&] { return counter == ticket; }); + } + + bool try_lock() + { + std::lock_guard autolock(cv_lock); + if (counter != next_ticket) + return false; + next_ticket++; + return true; + } + + void unlock() + { + { + std::lock_guard autolock(cv_lock); + counter++; + } + cv.notify_all(); // intentionally outside lock + } + +private: + std::condition_variable cv; + std::mutex cv_lock; + uint_fast32_t next_ticket, counter; +}; From c1ea49940b678c1158167ff3dccee85a9e0df769 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 15 Sep 2024 23:06:03 +0200 Subject: [PATCH 137/200] Add questionable workaround for env lock contention --- src/emerge.cpp | 8 +++++++ src/emerge.h | 1 + src/server.cpp | 54 ++++++++++++++++++++++++++++++++++++++++-- src/server.h | 5 ++-- src/util/timetaker.cpp | 2 +- 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/emerge.cpp b/src/emerge.cpp index 2d0f67505..788e2b745 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -316,6 +316,12 @@ bool EmergeManager::enqueueBlockEmergeEx( } +size_t EmergeManager::getQueueSize() +{ + MutexAutoLock queuelock(m_queue_mutex); + return m_blocks_enqueued.size(); +} + bool EmergeManager::isBlockInQueue(v3s16 pos) { MutexAutoLock queuelock(m_queue_mutex); @@ -540,7 +546,9 @@ bool EmergeThread::popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata) EmergeAction EmergeThread::getBlockOrStartGen(const v3s16 pos, bool allow_gen, const std::string *from_db, MapBlock **block, BlockMakeData *bmdata) { + //TimeTaker tt("", nullptr, PRECISION_MICRO); Server::EnvAutoLock envlock(m_server); + //g_profiler->avg("EmergeThread: lock wait time [us]", tt.stop()); auto block_ok = [] (MapBlock *b) { return b && b->isGenerated(); diff --git a/src/emerge.h b/src/emerge.h index 4e0f738d8..cbdcc4c7c 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -196,6 +196,7 @@ public: EmergeCompletionCallback callback, void *callback_param); + size_t getQueueSize(); bool isBlockInQueue(v3s16 pos); Mapgen *getCurrentMapgen(); diff --git a/src/server.cpp b/src/server.cpp index 8a45d7369..ddafa6312 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -133,9 +133,13 @@ void *ServerThread::run() u64 t0 = porting::getTimeUs(); - const Server::StepSettings step_settings = m_server->getStepSettings(); + const auto step_settings = m_server->getStepSettings(); try { + // see explanation inside + if (dtime > step_settings.steplen) + m_server->yieldToOtherThreads(dtime); + m_server->AsyncRunStep(step_settings.pause ? 0.0f : dtime); const float remaining_time = step_settings.steplen @@ -655,7 +659,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step) { EnvAutoLock lock(this); float max_lag = m_env->getMaxLagEstimate(); - constexpr float lag_warn_threshold = 2.0f; + constexpr float lag_warn_threshold = 1.0f; // Decrease value gradually, halve it every minute. if (m_max_lag_decrease.step(dtime, 0.5f)) { @@ -1113,6 +1117,52 @@ void Server::Receive(float timeout) } } +void Server::yieldToOtherThreads(float dtime) +{ + /* + * Problem: the server thread and emerge thread compete for the envlock. + * While the emerge thread needs it just once or twice for every processed item + * the server thread uses it much more generously. + * This is usually not a problem as the server sleeps between steps, which leaves + * enough chance. But if the server is overloaded it's busy all the time and + * - even with a fair envlock - the emerge thread can't get up to speed. + * This generally has a much worse impact on gameplay than server lag itself + * ever would. + * + * Workaround: If we detect that the server is overloaded, introduce some careful + * artificial sleeps to leave the emerge threads enough chance to do their job. + * + * In the future the emerge code should be reworked to exclusively use a result + * queue, thereby avoiding this problem (and terrible workaround). + */ + + // don't activate workaround too quickly + constexpr size_t MIN_EMERGE_QUEUE_SIZE = 32; + const size_t qs_initial = m_emerge->getQueueSize(); + if (qs_initial < MIN_EMERGE_QUEUE_SIZE) + return; + + // give the thread a chance to run for every 28ms (on average) + // this was experimentally determined + const float QUANTUM = 28.0f / 1000; + // put an upper limit to not cause too much lag, also so this doesn't become self-sustaining + const int SLEEP_MAX = 10; + + int sleep_count = std::clamp(dtime / QUANTUM, 1, SLEEP_MAX); + + ScopeProfiler sp(g_profiler, "Server::yieldTo...() sleep", SPT_AVG); + size_t qs = qs_initial; + while (sleep_count-- > 0) { + sleep_ms(1); + // abort if we don't make progress + size_t qs2 = m_emerge->getQueueSize(); + if (qs2 >= qs || qs2 == 0) + break; + qs = qs2; + } + g_profiler->avg("Server::yieldTo...() progress [#]", qs_initial - qs); +} + PlayerSAO* Server::StageTwoClientInit(session_t peer_id) { std::string playername; diff --git a/src/server.h b/src/server.h index 6b48d929d..57b543c11 100644 --- a/src/server.h +++ b/src/server.h @@ -167,9 +167,12 @@ public: // Actual processing is done in another thread. // This just checks if there was an error in that thread. void step(); + // This is run by ServerThread and does the actual processing void AsyncRunStep(float dtime, bool initial_step = false); void Receive(float timeout); + void yieldToOtherThreads(float dtime); + PlayerSAO* StageTwoClientInit(session_t peer_id); /* @@ -602,8 +605,6 @@ private: */ PlayerSAO *emergePlayer(const char *name, session_t peer_id, u16 proto_version); - void handlePeerChanges(); - /* Variables */ diff --git a/src/util/timetaker.cpp b/src/util/timetaker.cpp index a18d813ba..47d8ab83a 100644 --- a/src/util/timetaker.cpp +++ b/src/util/timetaker.cpp @@ -35,7 +35,7 @@ u64 TimeTaker::stop(bool quiet) if (m_result != nullptr) { (*m_result) += dtime; } else { - if (!quiet) { + if (!quiet && !m_name.empty()) { infostream << m_name << " took " << dtime << TimePrecision_units[m_precision] << std::endl; } From d08d34d80338aaad2fe963c10b081ee32676561a Mon Sep 17 00:00:00 2001 From: sfence Date: Thu, 26 Sep 2024 17:32:55 +0200 Subject: [PATCH 138/200] ABM without_neighbors (#14116) --- builtin/game/features.lua | 1 + doc/lua_api.md | 7 ++ games/devtest/mods/testabms/README.md | 6 ++ games/devtest/mods/testabms/after_node.lua | 12 +++ games/devtest/mods/testabms/chances.lua | 56 ++++++++++ games/devtest/mods/testabms/init.lua | 7 ++ games/devtest/mods/testabms/intervals.lua | 56 ++++++++++ games/devtest/mods/testabms/min_max.lua | 58 ++++++++++ games/devtest/mods/testabms/mod.conf | 2 + games/devtest/mods/testabms/neighbors.lua | 99 ++++++++++++++++++ .../testabms/textures/testabms_after_node.png | Bin 0 -> 179 bytes .../testabms/textures/testabms_wait_node.png | Bin 0 -> 183 bytes src/script/cpp_api/s_env.cpp | 17 ++- src/serverenvironment.cpp | 27 ++++- src/serverenvironment.h | 3 + src/unittest/test_servermodmanager.cpp | 2 +- 16 files changed, 347 insertions(+), 6 deletions(-) create mode 100644 games/devtest/mods/testabms/README.md create mode 100644 games/devtest/mods/testabms/after_node.lua create mode 100644 games/devtest/mods/testabms/chances.lua create mode 100644 games/devtest/mods/testabms/init.lua create mode 100644 games/devtest/mods/testabms/intervals.lua create mode 100644 games/devtest/mods/testabms/min_max.lua create mode 100644 games/devtest/mods/testabms/mod.conf create mode 100644 games/devtest/mods/testabms/neighbors.lua create mode 100644 games/devtest/mods/testabms/textures/testabms_after_node.png create mode 100644 games/devtest/mods/testabms/textures/testabms_wait_node.png diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 81b291e6c..10884497c 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -44,6 +44,7 @@ core.features = { override_item_remove_fields = true, hotbar_hud_element = true, bulk_lbms = true, + abm_without_neighbors = true, } function core.has_feature(arg) diff --git a/doc/lua_api.md b/doc/lua_api.md index 66a83542e..4c436b1d2 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5524,6 +5524,8 @@ Utilities hotbar_hud_element = true, -- Bulk LBM support (5.10.0) bulk_lbms = true, + -- ABM supports field without_neighbors (5.10.0) + abm_without_neighbors = true, } ``` @@ -9106,6 +9108,11 @@ Used by `minetest.register_abm`. -- If left out or empty, any neighbor will do. -- `group:groupname` can also be used here. + without_neighbors = {"default:lava_source", "default:lava_flowing"}, + -- Only apply `action` to nodes that have no one of these neighbors. + -- If left out or empty, it has no effect. + -- `group:groupname` can also be used here. + interval = 10.0, -- Operation interval in seconds diff --git a/games/devtest/mods/testabms/README.md b/games/devtest/mods/testabms/README.md new file mode 100644 index 000000000..60fa6d656 --- /dev/null +++ b/games/devtest/mods/testabms/README.md @@ -0,0 +1,6 @@ +# Test ABMs + +This mod contains a nodes and related ABM actions. +By placing these nodes, you can test basic ABM behaviours. + +There are separate tests for ABM `chance`, `interval`, `min_y`, `max_y`, `neighbor` and `without_neighbor` fields. diff --git a/games/devtest/mods/testabms/after_node.lua b/games/devtest/mods/testabms/after_node.lua new file mode 100644 index 000000000..64cdfb484 --- /dev/null +++ b/games/devtest/mods/testabms/after_node.lua @@ -0,0 +1,12 @@ + +local S = minetest.get_translator("testnodes") + +-- After ABM node +minetest.register_node("testabms:after_abm", { + description = S("After ABM processed node."), + drawtype = "normal", + tiles = { "testabms_after_node.png" }, + + groups = { dig_immediate = 3 }, +}) + diff --git a/games/devtest/mods/testabms/chances.lua b/games/devtest/mods/testabms/chances.lua new file mode 100644 index 000000000..95f416b45 --- /dev/null +++ b/games/devtest/mods/testabms/chances.lua @@ -0,0 +1,56 @@ +-- test ABMs with different chances + +local S = minetest.get_translator("testnodes") + +-- ABM chance 5 node +minetest.register_node("testabms:chance_5", { + description = S("Node for test ABM chance_5"), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Waiting for ABM testabms:chance_5") + end, +}) + +minetest.register_abm({ + label = "testabms:chance_5", + nodenames = "testabms:chance_5", + interval = 10, + chance = 5, + action = function (pos) + minetest.swap_node(pos, {name="testabms:after_abm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "ABM testabsm:chance_5 changed this node.") + end +}) + +-- ABM chance 20 node +minetest.register_node("testabms:chance_20", { + description = S("Node for test ABM chance_20"), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Waiting for ABM testabms:chance_20") + end, +}) + +minetest.register_abm({ + label = "testabms:chance_20", + nodenames = "testabms:chance_20", + interval = 10, + chance = 20, + action = function (pos) + minetest.swap_node(pos, {name="testabms:after_abm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "ABM testabsm:chance_20 changed this node.") + end +}) + diff --git a/games/devtest/mods/testabms/init.lua b/games/devtest/mods/testabms/init.lua new file mode 100644 index 000000000..7830d8436 --- /dev/null +++ b/games/devtest/mods/testabms/init.lua @@ -0,0 +1,7 @@ +local path = minetest.get_modpath(minetest.get_current_modname()) + +dofile(path.."/after_node.lua") +dofile(path.."/chances.lua") +dofile(path.."/intervals.lua") +dofile(path.."/min_max.lua") +dofile(path.."/neighbors.lua") diff --git a/games/devtest/mods/testabms/intervals.lua b/games/devtest/mods/testabms/intervals.lua new file mode 100644 index 000000000..57b58faa5 --- /dev/null +++ b/games/devtest/mods/testabms/intervals.lua @@ -0,0 +1,56 @@ +-- test ABMs with different interval + +local S = minetest.get_translator("testnodes") + +-- ABM inteval 1 node +minetest.register_node("testabms:interval_1", { + description = S("Node for test ABM interval_1"), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Waiting for ABM testabms:interval_1") + end, +}) + +minetest.register_abm({ + label = "testabms:interval_1", + nodenames = "testabms:interval_1", + interval = 1, + chance = 1, + action = function (pos) + minetest.swap_node(pos, {name="testabms:after_abm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "ABM testabsm:interval_1 changed this node.") + end +}) + +-- ABM interval 60 node +minetest.register_node("testabms:interval_60", { + description = S("Node for test ABM interval_60"), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Waiting for ABM testabms:interval_60") + end, +}) + +minetest.register_abm({ + label = "testabms:interval_60", + nodenames = "testabms:interval_60", + interval = 60, + chance = 1, + action = function (pos) + minetest.swap_node(pos, {name="testabms:after_abm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "ABM testabsm:interval_60 changed this node.") + end +}) + diff --git a/games/devtest/mods/testabms/min_max.lua b/games/devtest/mods/testabms/min_max.lua new file mode 100644 index 000000000..62f1ccd53 --- /dev/null +++ b/games/devtest/mods/testabms/min_max.lua @@ -0,0 +1,58 @@ +-- test ABMs with min_y and max_y + +local S = minetest.get_translator("testnodes") + +-- ABM min_y node +minetest.register_node("testabms:min_y", { + description = S("Node for test ABM min_y."), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Waiting for ABM testabms:min_y at y "..pos.y.." with min_y = 0") + end, +}) + +minetest.register_abm({ + label = "testabms:min_y", + nodenames = "testabms:min_y", + interval = 10, + chance = 1, + min_y = 0, + action = function (pos) + minetest.swap_node(pos, {name="testabms:after_abm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "ABM testabsm:min_y changed this node.") + end +}) + +-- ABM max_y node +minetest.register_node("testabms:max_y", { + description = S("Node for test ABM max_y."), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Waiting for ABM testabms:max_y at y "..pos.y.." with max_y = 0") + end, +}) + +minetest.register_abm({ + label = "testabms:max_y", + nodenames = "testabms:max_y", + interval = 10, + chance = 1, + max_y = 0, + action = function (pos) + minetest.swap_node(pos, {name="testabms:after_abm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "ABM testabsm:max_y changed this node.") + end +}) + diff --git a/games/devtest/mods/testabms/mod.conf b/games/devtest/mods/testabms/mod.conf new file mode 100644 index 000000000..ad74cd2ed --- /dev/null +++ b/games/devtest/mods/testabms/mod.conf @@ -0,0 +1,2 @@ +name = testabms +description = Contains some nodes for test ABMs. diff --git a/games/devtest/mods/testabms/neighbors.lua b/games/devtest/mods/testabms/neighbors.lua new file mode 100644 index 000000000..42ce47dff --- /dev/null +++ b/games/devtest/mods/testabms/neighbors.lua @@ -0,0 +1,99 @@ +-- test ABMs with neighbor and without_neighbor + +local S = minetest.get_translator("testnodes") + +-- ABM required neighbor +minetest.register_node("testabms:required_neighbor", { + description = S("Node for test ABM required_neighbor.") .. "\n" + .. S("Sensitive neighbor node is testnodes:normal."), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", + "Waiting for ABM testabms:required_neighbor " + .. "(normal drawtype testnode sensitive)") + end, +}) + +minetest.register_abm({ + label = "testabms:required_neighbor", + nodenames = "testabms:required_neighbor", + neighbors = {"testnodes:normal"}, + interval = 1, + chance = 1, + action = function (pos) + minetest.swap_node(pos, {name="testabms:after_abm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", + "ABM testabsm:required_neighbor changed this node.") + end +}) + +-- ABM missing neighbor node +minetest.register_node("testabms:missing_neighbor", { + description = S("Node for test ABM missing_neighbor.") .. "\n" + .. S("Sensitive neighbor node is testnodes:normal."), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", + "Waiting for ABM testabms:missing_neighbor" + .. " (normal drawtype testnode sensitive)") + end, +}) + +minetest.register_abm({ + label = "testabms:missing_neighbor", + nodenames = "testabms:missing_neighbor", + without_neighbors = {"testnodes:normal"}, + interval = 1, + chance = 1, + action = function (pos) + minetest.swap_node(pos, {name="testabms:after_abm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", + "ABM testabsm:missing_neighbor changed this node.") + end +}) + +-- ABM required and missing neighbor node +minetest.register_node("testabms:required_missing_neighbor", { + description = S("Node for test ABM required_missing_neighbor.") .. "\n" + .. S("Sensitive neighbor nodes are testnodes:normal and testnodes:glasslike."), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", + "Waiting for ABM testabms:required_missing_neighbor" + .. " (wint normal drawtype testnode and no glasslike" + .. " drawtype testnode sensitive)") + end, +}) + +minetest.register_abm({ + label = "testabms:required_missing_neighbor", + nodenames = "testabms:required_missing_neighbor", + neighbors = {"testnodes:normal"}, + without_neighbors = {"testnodes:glasslike"}, + interval = 1, + chance = 1, + action = function (pos) + minetest.swap_node(pos, {name="testabms:after_abm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", + "ABM testabsm:required_missing_neighbor changed this node.") + end +}) + diff --git a/games/devtest/mods/testabms/textures/testabms_after_node.png b/games/devtest/mods/testabms/textures/testabms_after_node.png new file mode 100644 index 0000000000000000000000000000000000000000..dab87594b998dde660a623a10cb6e8fe9a1a8b74 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#VfSXm-H;@Q#K(^opENwkdiDLntV2h`_qQKreZkP3#AD~DpHR%m*I>YK zwjGT(@8;C;|3_=v}$Oz_?mj^GY`-UXGp4=yt@ Y%$;W#T>IM27-%blr>mdKI;Vst0G7r(4*&oF literal 0 HcmV?d00001 diff --git a/games/devtest/mods/testabms/textures/testabms_wait_node.png b/games/devtest/mods/testabms/textures/testabms_wait_node.png new file mode 100644 index 0000000000000000000000000000000000000000..a9bd9a36f78fdc973c949fb4b9ded1d444215edd GIT binary patch literal 183 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vfj|2BG;e?M9j;d3T7>A%)9PS@tiKNgrtl`-%3-M7kz|#}Bta^XmL%oFUpD#X->OMTD=4o-qQx2oT_|ManSMX`=ZSV<~ e)wuhgoj>ufCtviv(>g$#89ZJ6T-G@yGywp@vqLce literal 0 HcmV?d00001 diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index deac90f3c..007622d52 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -34,10 +34,11 @@ with this program; if not, write to the Free Software Foundation, Inc., class LuaABM : public ActiveBlockModifier { private: - int m_id; + const int m_id; std::vector m_trigger_contents; std::vector m_required_neighbors; + std::vector m_without_neighbors; float m_trigger_interval; u32 m_trigger_chance; bool m_simple_catch_up; @@ -47,11 +48,13 @@ public: LuaABM(int id, const std::vector &trigger_contents, const std::vector &required_neighbors, + const std::vector &without_neighbors, float trigger_interval, u32 trigger_chance, bool simple_catch_up, s16 min_y, s16 max_y): m_id(id), m_trigger_contents(trigger_contents), m_required_neighbors(required_neighbors), + m_without_neighbors(without_neighbors), m_trigger_interval(trigger_interval), m_trigger_chance(trigger_chance), m_simple_catch_up(simple_catch_up), @@ -67,6 +70,10 @@ public: { return m_required_neighbors; } + virtual const std::vector &getWithoutNeighbors() const + { + return m_without_neighbors; + } virtual float getTriggerInterval() { return m_trigger_interval; @@ -230,6 +237,11 @@ void ScriptApiEnv::readABMs() read_nodenames(L, -1, required_neighbors); lua_pop(L, 1); + std::vector without_neighbors; + lua_getfield(L, current_abm, "without_neighbors"); + read_nodenames(L, -1, without_neighbors); + lua_pop(L, 1); + float trigger_interval = 10.0; getfloatfield(L, current_abm, "interval", trigger_interval); @@ -250,7 +262,8 @@ void ScriptApiEnv::readABMs() lua_pop(L, 1); LuaABM *abm = new LuaABM(id, trigger_contents, required_neighbors, - trigger_interval, trigger_chance, simple_catch_up, min_y, max_y); + without_neighbors, trigger_interval, trigger_chance, + simple_catch_up, min_y, max_y); env->addActiveBlockModifier(abm); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index ac627dd50..813184de1 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -827,6 +827,7 @@ struct ActiveABM { ActiveBlockModifier *abm; std::vector required_neighbors; + std::vector without_neighbors; int chance; s16 min_y, max_y; }; @@ -885,6 +886,10 @@ public: ndef->getIds(s, aabm.required_neighbors); SORT_AND_UNIQUE(aabm.required_neighbors); + for (const auto &s : abm->getWithoutNeighbors()) + ndef->getIds(s, aabm.without_neighbors); + SORT_AND_UNIQUE(aabm.without_neighbors); + // Trigger contents std::vector ids; for (const auto &s : abm->getTriggerContents()) @@ -996,8 +1001,11 @@ public: continue; // Check neighbors - if (!aabm.required_neighbors.empty()) { + const bool check_required_neighbors = !aabm.required_neighbors.empty(); + const bool check_without_neighbors = !aabm.without_neighbors.empty(); + if (check_required_neighbors || check_without_neighbors) { v3s16 p1; + bool have_required = false; for(p1.X = p0.X-1; p1.X <= p0.X+1; p1.X++) for(p1.Y = p0.Y-1; p1.Y <= p0.Y+1; p1.Y++) for(p1.Z = p0.Z-1; p1.Z <= p0.Z+1; p1.Z++) @@ -1015,12 +1023,25 @@ public: MapNode n = map->getNode(p1 + block->getPosRelative()); c = n.getContent(); } - if (CONTAINS(aabm.required_neighbors, c)) - goto neighbor_found; + if (check_required_neighbors && !have_required) { + if (CONTAINS(aabm.required_neighbors, c)) { + if (!check_without_neighbors) + goto neighbor_found; + have_required = true; + } + } + if (check_without_neighbors) { + if (CONTAINS(aabm.without_neighbors, c)) + goto neighbor_invalid; + } } + if (have_required || !check_required_neighbors) + goto neighbor_found; // No required neighbor found + neighbor_invalid: continue; } + neighbor_found: abms_run++; diff --git a/src/serverenvironment.h b/src/serverenvironment.h index d20cc0b3f..0b00fac91 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -63,6 +63,9 @@ public: // Set of required neighbors (trigger doesn't happen if none are found) // Empty = do not check neighbors virtual const std::vector &getRequiredNeighbors() const = 0; + // Set of without neighbors (trigger doesn't happen if any are found) + // Empty = do not check neighbors + virtual const std::vector &getWithoutNeighbors() const = 0; // Trigger interval in seconds virtual float getTriggerInterval() = 0; // Random chance of (1 / return value), 0 is disallowed diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index f26734ab3..03fdc7042 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -122,7 +122,7 @@ void TestServerModManager::testGetMods() ServerModManager sm(m_worlddir); const auto &mods = sm.getMods(); // `ls ./games/devtest/mods | wc -l` + 1 (test mod) - UASSERTEQ(std::size_t, mods.size(), 32 + 1); + UASSERTEQ(std::size_t, mods.size(), 33 + 1); // Ensure we found basenodes mod (part of devtest) // and test_mod (for testing MINETEST_MOD_PATH). From 65ec371b7849606e7a8b6bf31e75d7c210ce7035 Mon Sep 17 00:00:00 2001 From: DragonWrangler1 <146014546+DragonWrangler1@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:34:16 -0700 Subject: [PATCH 139/200] Allow `allfaces` drawtypes to have 6 textures (#15175) --- doc/lua_api.md | 3 ++- games/devtest/mods/testnodes/drawtypes.lua | 17 +++++++++++++++++ src/client/content_mapblock.cpp | 18 +++++++++++------- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 4c436b1d2..7623c1c5d 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -1470,7 +1470,8 @@ Look for examples in `games/devtest` or `games/minetest_game`. 'Connected Glass'. * `allfaces` * Often used for partially-transparent nodes. - * External and internal sides of textures are visible. + * External sides of textures, and unlike other drawtypes, the external sides + of other blocks, are visible from the inside. * `allfaces_optional` * Often used for leaves nodes. * This switches between `normal`, `glasslike` and `allfaces` according to diff --git a/games/devtest/mods/testnodes/drawtypes.lua b/games/devtest/mods/testnodes/drawtypes.lua index 087d09eff..4a657b739 100644 --- a/games/devtest/mods/testnodes/drawtypes.lua +++ b/games/devtest/mods/testnodes/drawtypes.lua @@ -98,6 +98,23 @@ minetest.register_node("testnodes:allfaces", { groups = { dig_immediate = 3 }, }) +minetest.register_node("testnodes:allfaces_6", { + description = S("\"allfaces 6 Textures\" Drawtype Test Node").."\n".. + S("Transparent node with visible internal backfaces"), + drawtype = "allfaces", + paramtype = "light", + tiles = { + "testnodes_allfaces.png^[colorize:red", + "testnodes_allfaces.png^[colorize:orange", + "testnodes_allfaces.png^[colorize:yellow", + "testnodes_allfaces.png^[colorize:green", + "testnodes_allfaces.png^[colorize:blue", + "testnodes_allfaces.png^[colorize:purple" + }, + + groups = { dig_immediate = 3 }, +}) + local allfaces_optional_tooltip = "".. S("Rendering depends on 'leaves_style' setting:").."\n".. S("* 'fancy': transparent with visible internal backfaces").."\n".. diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 2a1352139..6ce3ca0f4 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -1016,13 +1016,6 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() } } -void MapblockMeshGenerator::drawAllfacesNode() -{ - static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2); - useTile(0, 0, 0); - drawAutoLightedCuboid(box); -} - void MapblockMeshGenerator::drawTorchlikeNode() { u8 wall = cur_node.n.getWallMounted(nodedef); @@ -1545,6 +1538,17 @@ namespace { }; } +void MapblockMeshGenerator::drawAllfacesNode() +{ + static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2); + TileSpec tiles[6]; + for (int face = 0; face < 6; face++) + getTile(nodebox_tile_dirs[face], &tiles[face]); + if (data->m_smooth_lighting) + getSmoothLightFrame(); + drawAutoLightedCuboid(box, nullptr, tiles, 6); +} + void MapblockMeshGenerator::drawNodeboxNode() { TileSpec tiles[6]; From fbb0e82679b35366ae72db42d357b5439d72f9d6 Mon Sep 17 00:00:00 2001 From: grorp Date: Fri, 27 Sep 2024 11:08:35 +0200 Subject: [PATCH 140/200] Fix uninitialized shadow tint regression from #14610 (#15197) * Fix uninitialized shadow tint This resulted in shadows having a different, random color each time I started a game * Fix formatting mistakes from the same PR --- src/lighting.h | 2 +- src/network/networkprotocol.h | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lighting.h b/src/lighting.h index 20f434112..fbf10b1c9 100644 --- a/src/lighting.h +++ b/src/lighting.h @@ -56,5 +56,5 @@ struct Lighting float shadow_intensity {0.0f}; float saturation {1.0f}; float volumetric_light_strength {0.0f}; - video::SColor shadow_tint; + video::SColor shadow_tint {255, 0, 0, 0}; }; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index cae2a3291..aeb827608 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -229,7 +229,7 @@ with this program; if not, write to the Free Software Foundation, Inc., [bump for 5.9.1] PROTOCOL VERSION 46: Move default hotbar from client-side C++ to server-side builtin Lua - Add shadow tint to Lighting packets + Add shadow tint to Lighting packets Add shadow color to CloudParam packets Move death screen to server and make it a regular formspec The server no longer triggers the hardcoded client-side death @@ -241,7 +241,6 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #define LATEST_PROTOCOL_VERSION 46 - #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) // Server's supported network protocol range @@ -1182,4 +1181,4 @@ enum InteractAction : u8 INTERACT_PLACE, // 3: place block or item (to abovesurface) INTERACT_USE, // 4: use item INTERACT_ACTIVATE // 5: rightclick air ("activate") -}; \ No newline at end of file +}; From 610ddaba7ce23248ec898ac68c44ca62c0ad73b4 Mon Sep 17 00:00:00 2001 From: sfence Date: Fri, 27 Sep 2024 21:34:52 +0200 Subject: [PATCH 141/200] Allow detection of damage greater than HP (#15160) Co-authored-by: Gregor Parzefall --- doc/lua_api.md | 5 ++ games/devtest/mods/unittests/player.lua | 78 +++++++++++++++++++++---- src/server/player_sao.cpp | 9 +-- 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 7623c1c5d..9c888ff13 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5850,8 +5850,13 @@ Call these functions only at load time! * `clicker`: ObjectRef - Object that acted upon `player`, may or may not be a player * `minetest.register_on_player_hpchange(function(player, hp_change, reason), modifier)` * Called when the player gets damaged or healed + * When `hp == 0`, damage doesn't trigger this callback. + * When `hp == hp_max`, healing does still trigger this callback. * `player`: ObjectRef of the player * `hp_change`: the amount of change. Negative when it is damage. + * Historically, the new HP value was clamped to [0, 65535] before + calculating the HP change. This clamping has been removed as of + Minetest 5.10.0 * `reason`: a PlayerHPChangeReason table. * The `type` field will have one of the following values: * `set_hp`: A mod or the engine called `set_hp` without diff --git a/games/devtest/mods/unittests/player.lua b/games/devtest/mods/unittests/player.lua index 7650d5f57..f8945f320 100644 --- a/games/devtest/mods/unittests/player.lua +++ b/games/devtest/mods/unittests/player.lua @@ -42,41 +42,97 @@ unittests.register("test_hpchangereason", run_hpchangereason_tests, {player=true -- local expected_diff = nil +local hpchange_counter = 0 +local die_counter = 0 core.register_on_player_hpchange(function(player, hp_change, reason) if expected_diff then assert(hp_change == expected_diff) + hpchange_counter = hpchange_counter + 1 end end) +core.register_on_dieplayer(function() + die_counter = die_counter + 1 +end) + +local function hp_diference_test(player, hp_max) + assert(hp_max >= 22) -local function run_hp_difference_tests(player) local old_hp = player:get_hp() local old_hp_max = player:get_properties().hp_max - expected_diff = nil - player:set_properties({hp_max = 30}) - player:set_hp(22) + hpchange_counter = 0 + die_counter = 0 - -- final HP value is clamped to >= 0 before difference calculation - expected_diff = -22 + expected_diff = nil + player:set_properties({hp_max = hp_max}) + player:set_hp(22) + assert(player:get_hp() == 22) + assert(hpchange_counter == 0) + assert(die_counter == 0) + + -- HP difference is not clamped + expected_diff = -25 player:set_hp(-3) - -- and actual final HP value is clamped to >= 0 too + -- actual final HP value is clamped to >= 0 assert(player:get_hp() == 0) + assert(hpchange_counter == 1) + assert(die_counter == 1) expected_diff = 22 player:set_hp(22) assert(player:get_hp() == 22) + assert(hpchange_counter == 2) + assert(die_counter == 1) - -- final HP value is clamped to <= U16_MAX before difference calculation - expected_diff = 65535 - 22 + -- Integer overflow is prevented + -- so result is S32_MIN, not S32_MIN - 22 + expected_diff = -2147483648 + player:set_hp(-2147483648) + -- actual final HP value is clamped to >= 0 + assert(player:get_hp() == 0) + assert(hpchange_counter == 3) + assert(die_counter == 2) + + -- Damage is ignored if player is already dead (hp == 0) + expected_diff = "never equal" + player:set_hp(-11) + assert(player:get_hp() == 0) + -- no on_player_hpchange or on_dieplayer call expected + assert(hpchange_counter == 3) + assert(die_counter == 2) + + expected_diff = 11 + player:set_hp(11) + assert(player:get_hp() == 11) + assert(hpchange_counter == 4) + assert(die_counter == 2) + + -- HP difference is not clamped + expected_diff = 1000000 - 11 player:set_hp(1000000) - -- and actual final HP value is clamped to <= hp_max - assert(player:get_hp() == 30) + -- actual final HP value is clamped to <= hp_max + assert(player:get_hp() == hp_max) + assert(hpchange_counter == 5) + assert(die_counter == 2) + + -- "Healing" is not ignored when hp == hp_max + expected_diff = 80000 - hp_max + player:set_hp(80000) + assert(player:get_hp() == hp_max) + -- on_player_hpchange_call expected + assert(hpchange_counter == 6) + assert(die_counter == 2) expected_diff = nil player:set_properties({hp_max = old_hp_max}) player:set_hp(old_hp) core.close_formspec(player:get_player_name(), "") -- hide death screen end +local function run_hp_difference_tests(player) + hp_diference_test(player, 22) + hp_diference_test(player, 30) + hp_diference_test(player, 65535) -- U16_MAX +end unittests.register("test_hp_difference", run_hp_difference_tests, {player=true}) -- diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 30c41bb1e..61d328ca7 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -519,12 +519,13 @@ void PlayerSAO::rightClick(ServerActiveObject *clicker) void PlayerSAO::setHP(s32 target_hp, const PlayerHPChangeReason &reason, bool from_client) { - target_hp = rangelim(target_hp, 0, U16_MAX); - - if (target_hp == m_hp) + if (target_hp == m_hp || (m_hp == 0 && target_hp < 0)) return; // Nothing to do - s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, target_hp - (s32)m_hp, reason); + // Protect against overflow. + s32 hp_change = std::max((s64)target_hp - (s64)m_hp, S32_MIN); + + hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp_change, reason); hp_change = std::min(hp_change, U16_MAX); // Protect against overflow s32 hp = (s32)m_hp + hp_change; From 700fbc803d7fb393863074beb9e6c86e6883f003 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 25 Sep 2024 23:24:30 +0200 Subject: [PATCH 142/200] Minor improvements to metadata handling --- src/itemstackmetadata.cpp | 4 ++-- src/nodemetadata.cpp | 19 ++++++++++--------- src/nodemetadata.h | 5 ++++- src/script/lua_api/l_itemstackmeta.cpp | 6 +----- src/script/lua_api/l_nodemeta.cpp | 21 +++++++++++++-------- src/script/lua_api/l_playermeta.cpp | 5 +---- 6 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/itemstackmetadata.cpp b/src/itemstackmetadata.cpp index be1715e1a..a2fc67c46 100644 --- a/src/itemstackmetadata.cpp +++ b/src/itemstackmetadata.cpp @@ -89,11 +89,11 @@ void ItemStackMetadata::deSerialize(std::istream &is) while (!fnd.at_end()) { std::string name = fnd.next(DESERIALIZE_KV_DELIM_STR); std::string var = fnd.next(DESERIALIZE_PAIR_DELIM_STR); - m_stringvars[name] = var; + m_stringvars[name] = std::move(var); } } else { // BACKWARDS COMPATIBILITY - m_stringvars[""] = in; + m_stringvars[""] = std::move(in); } } updateToolCapabilities(); diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index a11503ebe..a86db15ad 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -62,14 +62,14 @@ void NodeMetadata::serialize(std::ostream &os, u8 version, bool disk) const void NodeMetadata::deSerialize(std::istream &is, u8 version) { clear(); - int num_vars = readU32(is); - for(int i=0; i= 2) { if (readU8(is) == 1) - markPrivate(name, true); + m_privatevars.insert(name); } } @@ -89,12 +89,12 @@ bool NodeMetadata::empty() const } -void NodeMetadata::markPrivate(const std::string &name, bool set) +bool NodeMetadata::markPrivate(const std::string &name, bool set) { if (set) - m_privatevars.insert(name); + return m_privatevars.insert(name).second; else - m_privatevars.erase(name); + return m_privatevars.erase(name) > 0; } int NodeMetadata::countNonPrivate() const @@ -144,6 +144,8 @@ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, writeS16(os, p.Z); } else { // Serialize positions within a mapblock + static_assert(MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE <= U16_MAX, + "position too big to serialize"); u16 p16 = (p.Z * MAP_BLOCKSIZE + p.Y) * MAP_BLOCKSIZE + p.X; writeU16(os, p16); } @@ -246,8 +248,7 @@ void NodeMetadataList::set(v3s16 p, NodeMetadata *d) void NodeMetadataList::clear() { if (m_is_metadata_owner) { - NodeMetadataMap::const_iterator it; - for (it = m_data.begin(); it != m_data.end(); ++it) + for (auto it = m_data.begin(); it != m_data.end(); ++it) delete it->second; } m_data.clear(); diff --git a/src/nodemetadata.h b/src/nodemetadata.h index da277aabd..3c2a67f53 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -57,7 +57,10 @@ public: { return m_privatevars.count(name) != 0; } - void markPrivate(const std::string &name, bool set); + + /// Marks a key as private. + /// @return metadata modified? + bool markPrivate(const std::string &name, bool set); private: int countNonPrivate() const; diff --git a/src/script/lua_api/l_itemstackmeta.cpp b/src/script/lua_api/l_itemstackmeta.cpp index ebabf7bae..730fab3b4 100644 --- a/src/script/lua_api/l_itemstackmeta.cpp +++ b/src/script/lua_api/l_itemstackmeta.cpp @@ -41,7 +41,7 @@ void ItemStackMetaRef::clearMeta() void ItemStackMetaRef::reportMetadataChange(const std::string *name) { - // TODO + // nothing to do } // Exported functions @@ -89,7 +89,6 @@ ItemStackMetaRef::~ItemStackMetaRef() void ItemStackMetaRef::create(lua_State *L, LuaItemStack *istack) { ItemStackMetaRef *o = new ItemStackMetaRef(istack); - //infostream<<"NodeMetaRef::create: o="<(getmeta(false)); + bool is_private_change = meta && name && meta->isPrivate(*name); + // If the metadata is now empty, get rid of it if (meta && meta->empty()) { clearMeta(); @@ -67,7 +69,7 @@ void NodeMetaRef::reportMetadataChange(const std::string *name) MapEditEvent event; event.type = MEET_BLOCK_NODE_METADATA_CHANGED; event.setPositionModified(m_p); - event.is_private_change = name && meta && meta->isPrivate(*name); + event.is_private_change = is_private_change; m_env->getMap().dispatchEvent(event); } @@ -94,21 +96,24 @@ int NodeMetaRef::l_mark_as_private(lua_State *L) NodeMetaRef *ref = checkObject(L, 1); NodeMetadata *meta = dynamic_cast(ref->getmeta(true)); - assert(meta); + if (!meta) + return 0; + bool modified = false; if (lua_istable(L, 2)) { lua_pushnil(L); while (lua_next(L, 2) != 0) { // key at index -2 and value at index -1 luaL_checktype(L, -1, LUA_TSTRING); - meta->markPrivate(readParam(L, -1), true); + modified |= meta->markPrivate(readParam(L, -1), true); // removes value, keeps key for next iteration lua_pop(L, 1); } } else if (lua_isstring(L, 2)) { - meta->markPrivate(readParam(L, 2), true); + modified |= meta->markPrivate(readParam(L, 2), true); } - ref->reportMetadataChange(); + if (modified) + ref->reportMetadataChange(); return 0; } @@ -145,12 +150,13 @@ bool NodeMetaRef::handleFromTable(lua_State *L, int table, IMetadata *_meta) Inventory *inv = meta->getInventory(); lua_getfield(L, table, "inventory"); if (lua_istable(L, -1)) { + auto *gamedef = getGameDef(L); int inventorytable = lua_gettop(L); lua_pushnil(L); while (lua_next(L, inventorytable) != 0) { // key at index -2 and value at index -1 - std::string name = luaL_checkstring(L, -2); - read_inventory_list(L, -1, inv, name.c_str(), getServer(L)); + const char *name = luaL_checkstring(L, -2); + read_inventory_list(L, -1, inv, name, gamedef); lua_pop(L, 1); // Remove value, keep key for next iteration } lua_pop(L, 1); @@ -177,7 +183,6 @@ NodeMetaRef::NodeMetaRef(IMetadata *meta): void NodeMetaRef::create(lua_State *L, v3s16 p, ServerEnvironment *env) { NodeMetaRef *o = new NodeMetaRef(p, env); - //infostream<<"NodeMetaRef::create: o="< Date: Sat, 28 Sep 2024 11:08:42 +0200 Subject: [PATCH 143/200] Fix vertex color on OpenGL 3 closes #14985 --- client/shaders/cloud_shader/opengl_vertex.glsl | 4 ---- client/shaders/default_shader/opengl_vertex.glsl | 4 ---- client/shaders/minimap_shader/opengl_vertex.glsl | 4 ---- client/shaders/nodes_shader/opengl_vertex.glsl | 6 +----- client/shaders/object_shader/opengl_vertex.glsl | 4 ---- client/shaders/selection_shader/opengl_vertex.glsl | 4 ---- src/client/shader.cpp | 6 +++++- 7 files changed, 6 insertions(+), 26 deletions(-) diff --git a/client/shaders/cloud_shader/opengl_vertex.glsl b/client/shaders/cloud_shader/opengl_vertex.glsl index ebf4aae49..92f5de64b 100644 --- a/client/shaders/cloud_shader/opengl_vertex.glsl +++ b/client/shaders/cloud_shader/opengl_vertex.glsl @@ -8,11 +8,7 @@ void main(void) { gl_Position = mWorldViewProj * inVertexPosition; -#ifdef GL_ES - vec4 color = inVertexColor.bgra; -#else vec4 color = inVertexColor; -#endif color *= materialColor; varColor = color; diff --git a/client/shaders/default_shader/opengl_vertex.glsl b/client/shaders/default_shader/opengl_vertex.glsl index a908ac953..d95a3c2d3 100644 --- a/client/shaders/default_shader/opengl_vertex.glsl +++ b/client/shaders/default_shader/opengl_vertex.glsl @@ -3,9 +3,5 @@ varying lowp vec4 varColor; void main(void) { gl_Position = mWorldViewProj * inVertexPosition; -#ifdef GL_ES - varColor = inVertexColor.bgra; -#else varColor = inVertexColor; -#endif } diff --git a/client/shaders/minimap_shader/opengl_vertex.glsl b/client/shaders/minimap_shader/opengl_vertex.glsl index b23d27181..1a9491805 100644 --- a/client/shaders/minimap_shader/opengl_vertex.glsl +++ b/client/shaders/minimap_shader/opengl_vertex.glsl @@ -7,9 +7,5 @@ void main(void) { varTexCoord = inTexCoord0.st; gl_Position = mWorldViewProj * inVertexPosition; -#ifdef GL_ES - varColor = inVertexColor.bgra; -#else varColor = inVertexColor; -#endif } diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index ba48189a5..d5d6dd59e 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -199,15 +199,11 @@ void main(void) vNormal = inVertexNormal; // Calculate color. + vec4 color = inVertexColor; // Red, green and blue components are pre-multiplied with // the brightness, so now we have to multiply these // colors with the color of the incoming light. // The pre-baked colors are halved to prevent overflow. -#ifdef GL_ES - vec4 color = inVertexColor.bgra; -#else - vec4 color = inVertexColor; -#endif // The alpha gives the ratio of sunlight in the incoming light. nightRatio = 1.0 - color.a; color.rgb = color.rgb * (color.a * dayLight.rgb + diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl index 05134a5f6..4bb109f68 100644 --- a/client/shaders/object_shader/opengl_vertex.glsl +++ b/client/shaders/object_shader/opengl_vertex.glsl @@ -109,11 +109,7 @@ void main(void) : directional_ambient(normalize(inVertexNormal)); #endif -#ifdef GL_ES - vec4 color = inVertexColor.bgra; -#else vec4 color = inVertexColor; -#endif color *= materialColor; diff --git a/client/shaders/selection_shader/opengl_vertex.glsl b/client/shaders/selection_shader/opengl_vertex.glsl index 39dde3056..9ca87a9cf 100644 --- a/client/shaders/selection_shader/opengl_vertex.glsl +++ b/client/shaders/selection_shader/opengl_vertex.glsl @@ -6,9 +6,5 @@ void main(void) varTexCoord = inTexCoord0.st; gl_Position = mWorldViewProj * inVertexPosition; -#ifdef GL_ES - varColor = inVertexColor.bgra; -#else varColor = inVertexColor; -#endif } diff --git a/src/client/shader.cpp b/src/client/shader.cpp index ad37ea4c1..c0a2f109a 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -561,7 +561,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, // Create shaders header bool fully_programmable = driver->getDriverType() == video::EDT_OGLES2 || driver->getDriverType() == video::EDT_OPENGL3; - std::stringstream shaders_header; + std::ostringstream shaders_header; shaders_header << std::noboolalpha << std::showpoint // for GLSL ES @@ -588,10 +588,14 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, attribute mediump vec4 inVertexTangent; attribute mediump vec4 inVertexBinormal; )"; + // Our vertex color has components reversed compared to what OpenGL + // normally expects, so we need to take that into account. + vertex_header += "#define inVertexColor (inVertexColor.bgra)\n"; fragment_header = R"( precision mediump float; )"; } else { + /* legacy OpenGL driver */ shaders_header << R"( #version 120 #define lowp From 9e14f5f0538ed6fbc98ea47de6c132b756eef148 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 27 Sep 2024 19:04:59 +0200 Subject: [PATCH 144/200] Apply some fixes to server destruction order was broken by bc4ab8b99e8a9530f2a53152ff03608e278b4351 --- src/server.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/server.cpp b/src/server.cpp index ddafa6312..e57d81dbb 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -393,6 +393,10 @@ Server::~Server() infostream << "Server: Saving environment metadata" << std::endl; m_env->saveMeta(); + // Delete classes that depend on the environment + m_inventory_mgr.reset(); + m_script.reset(); + // Note that this also deletes and saves the map. delete m_env; m_env = nullptr; @@ -409,6 +413,9 @@ Server::~Server() } } + // emerge may depend on definition managers, so destroy first + m_emerge.reset(); + // Delete the rest in the reverse order of creation delete m_game_settings; delete m_banmanager; From bca44574d511ed49d50c4eaf8f5927063f7d40ed Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 27 Sep 2024 20:26:12 +0200 Subject: [PATCH 145/200] Add test script for server error cases --- .github/workflows/linux.yml | 7 +++++- util/helper_mod/error.lua | 1 + util/helper_mod/init.lua | 20 ++++++++++++++++ util/test_error_cases.sh | 46 +++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 util/helper_mod/error.lua create mode 100755 util/test_error_cases.sh diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2d0c72907..a2ec10a0a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -88,7 +88,7 @@ jobs: - name: Install deps run: | source ./util/ci/common.sh - install_linux_deps clang-7 llvm + install_linux_deps clang-7 llvm-7 - name: Build run: | @@ -102,6 +102,11 @@ jobs: run: | ./bin/minetest --run-unittests + # Do this here because we have ASan and error paths are sensitive to dangling pointers + - name: Test error cases + run: | + ./util/test_error_cases.sh + # Current clang version clang_18: runs-on: ubuntu-24.04 diff --git a/util/helper_mod/error.lua b/util/helper_mod/error.lua new file mode 100644 index 000000000..2a4b3e355 --- /dev/null +++ b/util/helper_mod/error.lua @@ -0,0 +1 @@ +error("intentional") diff --git a/util/helper_mod/init.lua b/util/helper_mod/init.lua index 4da832ed7..b2ed3b29d 100644 --- a/util/helper_mod/init.lua +++ b/util/helper_mod/init.lua @@ -48,4 +48,24 @@ elseif mode == "mapgen" then end core.after(0, next_, 1) +elseif mode == "error" then + + local n = tonumber(core.settings:get("error_type")) + local error_lua = core.get_modpath(core.get_current_modname()) .. "/error.lua" + if n == 1 then + print("=> error during startup <=") + error("intentional") + elseif n == 2 then + print("=> error on first step <=") + core.after(0, error, "intentional") + elseif n == 3 then + print("=> error in async script <=") + core.register_async_dofile(error_lua) + elseif n == 4 then + print("=> error in mapgen script <=") + core.register_mapgen_script(error_lua) + else + assert(false) + end + end diff --git a/util/test_error_cases.sh b/util/test_error_cases.sh new file mode 100755 index 000000000..801e097a3 --- /dev/null +++ b/util/test_error_cases.sh @@ -0,0 +1,46 @@ +#!/bin/bash +dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +gameid=${gameid:-devtest} +minetest=$dir/../bin/minetest +testspath=$dir/../tests +conf_server=$testspath/server.conf +worldpath=$testspath/world + +[ -e "$minetest" ] || { echo "executable $minetest missing"; exit 1; } + +write_config () { + printf '%s\n' >"$conf_server" \ + helper_mode=error mg_name=singlenode "$@" +} + +run () { + timeout 10 "$@" + r=$? + echo "Exit status: $r" + [ $r -eq 124 ] && echo "(timed out)" + if [ $r -ne 1 ]; then + echo "-> Test failed" + exit 1 + fi +} + +rm -rf "$worldpath" +mkdir -p "$worldpath/worldmods" + +ln -s "$dir/helper_mod" "$worldpath/worldmods/" + +args=(--server --config "$conf_server" --world "$worldpath" --gameid $gameid) + +# make sure we can tell apart sanitizer and minetest errors +export ASAN_OPTIONS="exitcode=42" +export MSAN_OPTIONS="exitcode=42" + +# see helper_mod/init.lua for the different types +for n in $(seq 1 4); do + write_config error_type=$n + run "$minetest" "${args[@]}" + echo "---------------" +done + +echo "All done." +exit 0 From c6fc694ea6d332f23e30c8624f42341380186f33 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Mon, 30 Sep 2024 16:41:53 -0400 Subject: [PATCH 146/200] Fix deletePathFromFilename returning cutoff filenames (#15211) --- irr/include/coreutil.h | 9 ++++----- irr/src/CFileList.cpp | 4 ++-- irr/src/CZipReader.cpp | 3 +-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/irr/include/coreutil.h b/irr/include/coreutil.h index 60014c4a7..73d1c4b43 100644 --- a/irr/include/coreutil.h +++ b/irr/include/coreutil.h @@ -63,7 +63,7 @@ inline io::path &getFileNameExtension(io::path &dest, const io::path &source) } //! delete path from filename -inline io::path &deletePathFromFilename(io::path &filename) +inline io::path deletePathFromFilename(const io::path &filename) { // delete path from filename const fschar_t *s = filename.c_str(); @@ -73,11 +73,10 @@ inline io::path &deletePathFromFilename(io::path &filename) while (*p != '/' && *p != '\\' && p != s) p--; - if (p != s) { + if (p != s) ++p; - filename = p; - } - return filename; + + return p; } //! trim paths diff --git a/irr/src/CFileList.cpp b/irr/src/CFileList.cpp index dde8e75ac..cd6c85df4 100644 --- a/irr/src/CFileList.cpp +++ b/irr/src/CFileList.cpp @@ -80,7 +80,7 @@ u32 CFileList::addItem(const io::path &fullPath, u32 offset, u32 size, bool isDi entry.FullName = entry.Name; - core::deletePathFromFilename(entry.Name); + entry.Name = core::deletePathFromFilename(entry.Name); if (IgnorePaths) entry.FullName = entry.Name; @@ -140,7 +140,7 @@ s32 CFileList::findFile(const io::path &filename, bool isDirectory = false) cons entry.FullName.make_lower(); if (IgnorePaths) - core::deletePathFromFilename(entry.FullName); + entry.FullName = core::deletePathFromFilename(entry.FullName); return Files.binary_search(entry); } diff --git a/irr/src/CZipReader.cpp b/irr/src/CZipReader.cpp index 2d2152719..036f6302a 100644 --- a/irr/src/CZipReader.cpp +++ b/irr/src/CZipReader.cpp @@ -191,8 +191,7 @@ bool CZipReader::scanGZipHeader() } } else { // no file name? - ZipFileName = Path; - core::deletePathFromFilename(ZipFileName); + ZipFileName = core::deletePathFromFilename(Path); // rename tgz to tar or remove gz extension if (core::hasFileExtension(ZipFileName, "tgz")) { From 53d949bd9fc01a7ce87c6a4eac5b8f2c82e637b6 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 30 Sep 2024 22:43:08 +0200 Subject: [PATCH 147/200] Discourage disabling shaders (#15210) --- builtin/settingtypes.txt | 14 +++++++------- src/client/shader.cpp | 26 +++++++++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index d13f25695..cffc728d1 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -262,7 +262,7 @@ viewing_range (Viewing range) int 190 20 4000 # Higher values result in a less detailed image. undersampling (Undersampling) int 1 1 8 -[**Graphics Effects] +[**Graphical Effects] # Allows liquids to be translucent. translucent_liquids (Translucent liquids) bool true @@ -468,12 +468,6 @@ enable_raytraced_culling (Enable Raytraced Culling) bool true [*Shaders] -# Shaders allow advanced visual effects and may increase performance on some video -# cards. -# -# Requires: shaders_support -enable_shaders (Shaders) bool true - [**Waving Nodes] # Set to true to enable waving leaves. @@ -1866,6 +1860,11 @@ ignore_world_load_errors (Ignore world errors) bool false [**Graphics] +# Shaders are a fundamental part of rendering and enable advanced visual effects. +# +# Requires: shaders_support +enable_shaders (Shaders) bool true + # Path to shader directory. If no path is defined, default location will be used. # # Requires: shaders @@ -1889,6 +1888,7 @@ cloud_radius (Cloud radius) int 12 1 62 desynchronize_mapblock_texture_animation (Desynchronize block animation) bool false # Enables caching of facedir rotated meshes. +# This is only effective with shaders disabled. enable_mesh_cache (Mesh cache) bool false # Delay between mesh updates on the client in ms. Increasing this will slow diff --git a/src/client/shader.cpp b/src/client/shader.cpp index c0a2f109a..d368ccb09 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -322,6 +322,9 @@ public: private: + // Are shaders even enabled? + bool m_enabled; + // The id of the thread that is allowed to use irrlicht directly std::thread::id m_main_thread; @@ -360,6 +363,12 @@ ShaderSource::ShaderSource() // Add a dummy ShaderInfo as the first index, named "" m_shaderinfo_cache.emplace_back(); + m_enabled = g_settings->getBool("enable_shaders"); + if (!m_enabled) { + warningstream << "You are running " PROJECT_NAME_C " with shaders disabled, " + "this is not a recommended configuration." << std::endl; + } + // Add main global constant setter addShaderConstantSetterFactory(new MainShaderConstantSetterFactory()); } @@ -368,9 +377,11 @@ ShaderSource::~ShaderSource() { MutexAutoLock lock(m_shaderinfo_cache_mutex); + if (!m_enabled) + return; + // Delete materials - video::IGPUProgrammingServices *gpu = RenderingEngine::get_video_driver()-> - getGPUProgrammingServices(); + auto *gpu = RenderingEngine::get_video_driver()->getGPUProgrammingServices(); for (ShaderInfo &i : m_shaderinfo_cache) { if (!i.name.empty()) gpu->deleteShaderMaterial(i.material); @@ -499,9 +510,11 @@ void ShaderSource::rebuildShaders() { MutexAutoLock lock(m_shaderinfo_cache_mutex); + if (!m_enabled) + return; + // Delete materials - video::IGPUProgrammingServices *gpu = RenderingEngine::get_video_driver()-> - getGPUProgrammingServices(); + auto *gpu = RenderingEngine::get_video_driver()->getGPUProgrammingServices(); for (ShaderInfo &i : m_shaderinfo_cache) { if (!i.name.empty()) { gpu->deleteShaderMaterial(i.material); @@ -548,12 +561,11 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, } shaderinfo.material = shaderinfo.base_material; - bool enable_shaders = g_settings->getBool("enable_shaders"); - if (!enable_shaders) + if (!m_enabled) return shaderinfo; video::IVideoDriver *driver = RenderingEngine::get_video_driver(); - video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices(); + auto *gpu = driver->getGPUProgrammingServices(); if (!driver->queryFeature(video::EVDF_ARB_GLSL) || !gpu) { throw ShaderException(gettext("Shaders are enabled but GLSL is not " "supported by the driver.")); From 6569fdd4d1c5058972f78be46a8e2f002348274f Mon Sep 17 00:00:00 2001 From: swagtoy Date: Mon, 30 Sep 2024 16:57:18 -0400 Subject: [PATCH 148/200] Add QT Creator and Windows dump files to `.gitignore` (#15214) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8ff758720..c7879380b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ tags !tags/ gtags.files .idea +.qtcreator/ # Codelite *.project # Visual Studio Code & plugins @@ -109,6 +110,8 @@ src/cmake_config_githash.h *.layout *.o *.a +*.dump +*.dmp *.ninja .ninja* *.gch From 22ef4c8be17528d7fa23c7a0557d49aeda6f2e3a Mon Sep 17 00:00:00 2001 From: grorp Date: Tue, 1 Oct 2024 17:21:42 +0200 Subject: [PATCH 149/200] Expose analog joystick input to the Lua API (#14348) --- doc/lua_api.md | 14 ++++-- src/client/client.cpp | 17 +++++-- src/client/game.cpp | 5 ++- src/client/inputhandler.cpp | 67 +++++----------------------- src/client/inputhandler.h | 16 +++---- src/client/localplayer.h | 2 + src/gui/touchcontrols.h | 4 +- src/network/networkprotocol.h | 2 + src/network/serverpackethandler.cpp | 14 +++++- src/player.cpp | 41 +++++++++++++++++ src/player.h | 8 +++- src/script/lua_api/l_localplayer.cpp | 13 +++--- src/script/lua_api/l_object.cpp | 7 +++ 13 files changed, 127 insertions(+), 83 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 9c888ff13..4bf7d31c1 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8249,12 +8249,18 @@ child will follow movement and rotation of that bone. bgcolor[], any non-style elements (eg: label) may result in weird behavior. * Only affects formspecs shown after this is called. * `get_formspec_prepend()`: returns a formspec string. -* `get_player_control()`: returns table with player pressed keys - * The table consists of fields with the following boolean values - representing the pressed keys: `up`, `down`, `left`, `right`, `jump`, - `aux1`, `sneak`, `dig`, `place`, `LMB`, `RMB`, and `zoom`. +* `get_player_control()`: returns table with player input + * The table contains the following boolean fields representing the pressed + keys: `up`, `down`, `left`, `right`, `jump`, `aux1`, `sneak`, `dig`, + `place`, `LMB`, `RMB` and `zoom`. * The fields `LMB` and `RMB` are equal to `dig` and `place` respectively, and exist only to preserve backwards compatibility. + * The table also contains the fields `movement_x` and `movement_y`. + * They represent the movement of the player. Values are numbers in the + range [-1.0,+1.0]. + * They take both keyboard and joystick input into account. + * You should prefer them over `up`, `down`, `left` and `right` to + support different input methods correctly. * Returns an empty table `{}` if the object is not a player. * `get_player_control_bits()`: returns integer with bit packed player pressed keys. diff --git a/src/client/client.cpp b/src/client/client.cpp index 1a2f51db9..a7b714069 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1034,7 +1034,7 @@ void Client::Send(NetworkPacket* pkt) m_con->Send(PEER_ID_SERVER, scf.channel, pkt, scf.reliable); } -// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 bytes +// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4 bytes void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt, bool camera_inverted) { v3f pf = myplayer->getPosition() * 100; @@ -1046,6 +1046,8 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * u8 fov = std::fmin(255.0f, clientMap->getCameraFov() * 80.0f); u8 wanted_range = std::fmin(255.0f, std::ceil(clientMap->getWantedRange() * (1.0f / MAP_BLOCKSIZE))); + f32 movement_speed = myplayer->control.movement_speed; + f32 movement_dir = myplayer->control.movement_direction; v3s32 position(pf.X, pf.Y, pf.Z); v3s32 speed(sf.X, sf.Y, sf.Z); @@ -1060,10 +1062,13 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * [12+12+4+4+4] u8 fov*80 [12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) [12+12+4+4+4+1+1] u8 camera_inverted (bool) + [12+12+4+4+4+1+1+1] f32 movement_speed + [12+12+4+4+4+1+1+1+4] f32 movement_direction */ *pkt << position << speed << pitch << yaw << keyPressed; *pkt << fov << wanted_range; *pkt << camera_inverted; + *pkt << movement_speed << movement_dir; } void Client::interact(InteractAction action, const PointedThing& pointed) @@ -1397,6 +1402,8 @@ void Client::sendPlayerPos() u32 keyPressed = player->control.getKeysPressed(); bool camera_inverted = m_camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT; + f32 movement_speed = player->control.movement_speed; + f32 movement_dir = player->control.movement_direction; if ( player->last_position == player->getPosition() && @@ -1406,7 +1413,9 @@ void Client::sendPlayerPos() player->last_keyPressed == keyPressed && player->last_camera_fov == camera_fov && player->last_camera_inverted == camera_inverted && - player->last_wanted_range == wanted_range) + player->last_wanted_range == wanted_range && + player->last_movement_speed == movement_speed && + player->last_movement_dir == movement_dir) return; player->last_position = player->getPosition(); @@ -1417,8 +1426,10 @@ void Client::sendPlayerPos() player->last_camera_fov = camera_fov; player->last_camera_inverted = camera_inverted; player->last_wanted_range = wanted_range; + player->last_movement_speed = movement_speed; + player->last_movement_dir = movement_dir; - NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1); + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4); writePlayerPos(player, &map, &pkt, camera_inverted); diff --git a/src/client/game.cpp b/src/client/game.cpp index 6cbf54f5a..9dd3a0e83 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2752,9 +2752,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::PLACE), cam.camera_pitch, cam.camera_yaw, - input->getMovementSpeed(), - input->getMovementDirection() + input->getJoystickSpeed(), + input->getJoystickDirection() ); + control.setMovementFromKeys(); // autoforward if set: move at maximum speed if (player->getPlayerSettings().continuous_forward && diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 39c212d2f..168ef1193 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -220,48 +220,19 @@ bool MyEventReceiver::OnEvent(const SEvent &event) /* * RealInputHandler */ -float RealInputHandler::getMovementSpeed() +float RealInputHandler::getJoystickSpeed() { - bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]), - b = m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]), - l = m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]), - r = m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]); - if (f || b || l || r) - { - // if contradictory keys pressed, stay still - if (f && b && l && r) - return 0.0f; - else if (f && b && !l && !r) - return 0.0f; - else if (!f && !b && l && r) - return 0.0f; - return 1.0f; // If there is a keyboard event, assume maximum speed - } - if (g_touchcontrols && g_touchcontrols->getMovementSpeed()) - return g_touchcontrols->getMovementSpeed(); + if (g_touchcontrols && g_touchcontrols->getJoystickSpeed()) + return g_touchcontrols->getJoystickSpeed(); return joystick.getMovementSpeed(); } -float RealInputHandler::getMovementDirection() +float RealInputHandler::getJoystickDirection() { - float x = 0, z = 0; - - /* Check keyboard for input */ - if (m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD])) - z += 1; - if (m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD])) - z -= 1; - if (m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT])) - x += 1; - if (m_receiver->IsKeyDown(keycache.key[KeyType::LEFT])) - x -= 1; - - if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */ - return std::atan2(x, z); - // `getMovementDirection() == 0` means forward, so we cannot use - // `getMovementDirection()` as a condition. - else if (g_touchcontrols && g_touchcontrols->getMovementSpeed()) - return g_touchcontrols->getMovementDirection(); + // `getJoystickDirection() == 0` means forward, so we cannot use + // `getJoystickDirection()` as a condition. + if (g_touchcontrols && g_touchcontrols->getJoystickSpeed()) + return g_touchcontrols->getJoystickDirection(); return joystick.getMovementDirection(); } @@ -320,25 +291,11 @@ void RandomInputHandler::step(float dtime) counterMovement -= dtime; if (counterMovement < 0.0) { counterMovement = 0.1 * Rand(1, 40); - movementSpeed = Rand(0,100)*0.01; - movementDirection = Rand(-100, 100)*0.01 * M_PI; + joystickSpeed = Rand(0,100)*0.01; + joystickDirection = Rand(-100, 100)*0.01 * M_PI; } } else { - bool f = keydown[keycache.key[KeyType::FORWARD]], - l = keydown[keycache.key[KeyType::LEFT]]; - if (f || l) { - movementSpeed = 1.0f; - if (f && !l) - movementDirection = 0.0; - else if (!f && l) - movementDirection = -M_PI_2; - else if (f && l) - movementDirection = -M_PI_4; - else - movementDirection = 0.0; - } else { - movementSpeed = 0.0; - movementDirection = 0.0; - } + joystickSpeed = 0.0f; + joystickDirection = 0.0f; } } diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index daf01c488..8efefce5b 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -247,8 +247,8 @@ public: virtual bool wasKeyReleased(GameKeyType k) = 0; virtual bool cancelPressed() = 0; - virtual float getMovementSpeed() = 0; - virtual float getMovementDirection() = 0; + virtual float getJoystickSpeed() = 0; + virtual float getJoystickDirection() = 0; virtual void clearWasKeyPressed() {} virtual void clearWasKeyReleased() {} @@ -304,9 +304,9 @@ public: return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k); } - virtual float getMovementSpeed(); + virtual float getJoystickSpeed(); - virtual float getMovementDirection(); + virtual float getJoystickDirection(); virtual bool cancelPressed() { @@ -388,8 +388,8 @@ public: virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } virtual bool cancelPressed() { return false; } - virtual float getMovementSpeed() { return movementSpeed; } - virtual float getMovementDirection() { return movementDirection; } + virtual float getJoystickSpeed() { return joystickSpeed; } + virtual float getJoystickDirection() { return joystickDirection; } virtual v2s32 getMousePos() { return mousepos; } virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); } @@ -403,6 +403,6 @@ private: KeyList keydown; v2s32 mousepos; v2s32 mousespeed; - float movementSpeed; - float movementDirection; + float joystickSpeed; + float joystickDirection; }; diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 815fafa8b..275f556e4 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -105,6 +105,8 @@ public: u8 last_camera_fov = 0; u8 last_wanted_range = 0; bool last_camera_inverted = false; + f32 last_movement_speed = 0.0f; + f32 last_movement_dir = 0.0f; float camera_impact = 0.0f; diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index 102c85f09..1787f6a5d 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -163,8 +163,8 @@ public: */ line3d getShootline() { return m_shootline; } - float getMovementDirection() { return m_joystick_direction; } - float getMovementSpeed() { return m_joystick_speed; } + float getJoystickDirection() { return m_joystick_direction; } + float getJoystickSpeed() { return m_joystick_speed; } void step(float dtime); inline void setUseCrosshair(bool use_crosshair) { m_draw_crosshair = use_crosshair; } diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index aeb827608..900492d05 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -962,6 +962,8 @@ enum ToServerCommand : u16 [2+12+12+4+4+4] u8 fov*80 [2+12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) [2+12+12+4+4+4+1+1] u8 camera_inverted (bool) + [2+12+12+4+4+4+1+1+1] f32 movement_speed + [2+12+12+4+4+4+1+1+1+4] f32 movement_direction */ diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index c17d32e41..3e60e8cc8 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -477,12 +477,24 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, u8 bits = 0; // bits instead of bool so it is extensible later *pkt >> keyPressed; + player->control.unpackKeysPressed(keyPressed); + *pkt >> f32fov; fov = (f32)f32fov / 80.0f; *pkt >> wanted_range; + if (pkt->getRemainingBytes() >= 1) *pkt >> bits; + if (pkt->getRemainingBytes() >= 8) { + *pkt >> player->control.movement_speed; + *pkt >> player->control.movement_direction; + } else { + player->control.movement_speed = 0.0f; + player->control.movement_direction = 0.0f; + player->control.setMovementFromKeys(); + } + v3f position((f32)ps.X / 100.0f, (f32)ps.Y / 100.0f, (f32)ps.Z / 100.0f); v3f speed((f32)ss.X / 100.0f, (f32)ss.Y / 100.0f, (f32)ss.Z / 100.0f); @@ -501,8 +513,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, playersao->setWantedRange(wanted_range); playersao->setCameraInverted(bits & 0x01); - player->control.unpackKeysPressed(keyPressed); - if (playersao->checkMovementCheat()) { // Call callbacks m_script->on_cheat(playersao, "moved_too_fast"); diff --git a/src/player.cpp b/src/player.cpp index fd25626ca..7361549e0 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -173,6 +173,42 @@ u16 Player::getMaxHotbarItemcount() return mainlist ? std::min(mainlist->getSize(), (u32) hud_hotbar_itemcount) : 0; } +void PlayerControl::setMovementFromKeys() +{ + bool a_up = direction_keys & (1 << 0), + a_down = direction_keys & (1 << 1), + a_left = direction_keys & (1 << 2), + a_right = direction_keys & (1 << 3); + + if (a_up || a_down || a_left || a_right) { + // if contradictory keys pressed, stay still + if (a_up && a_down && a_left && a_right) + movement_speed = 0.0f; + else if (a_up && a_down && !a_left && !a_right) + movement_speed = 0.0f; + else if (!a_up && !a_down && a_left && a_right) + movement_speed = 0.0f; + else + // If there is a keyboard event, assume maximum speed + movement_speed = 1.0f; + } + + // Check keyboard for input + float x = 0, y = 0; + if (a_up) + y += 1; + if (a_down) + y -= 1; + if (a_left) + x -= 1; + if (a_right) + x += 1; + + if (x != 0 || y != 0) + // If there is a keyboard event, it takes priority + movement_direction = std::atan2(x, y); +} + #ifndef SERVER u32 PlayerControl::getKeysPressed() const @@ -231,6 +267,11 @@ void PlayerControl::unpackKeysPressed(u32 keypress_bits) zoom = keypress_bits & (1 << 9); } +v2f PlayerControl::getMovement() const +{ + return v2f(std::sin(movement_direction), std::cos(movement_direction)) * movement_speed; +} + static auto tie(const PlayerPhysicsOverride &o) { // Make sure to add new members to this list! diff --git a/src/player.h b/src/player.h index 53411fea4..972a04e02 100644 --- a/src/player.h +++ b/src/player.h @@ -86,6 +86,11 @@ struct PlayerControl movement_direction = a_movement_direction; } + // Sets movement_speed and movement_direction according to direction_keys + // if direction_keys != 0, otherwise leaves them unchanged to preserve + // joystick input. + void setMovementFromKeys(); + #ifndef SERVER // For client use u32 getKeysPressed() const; @@ -94,6 +99,7 @@ struct PlayerControl // For server use void unpackKeysPressed(u32 keypress_bits); + v2f getMovement() const; u8 direction_keys = 0; bool jump = false; @@ -102,7 +108,7 @@ struct PlayerControl bool zoom = false; bool dig = false; bool place = false; - // Note: These four are NOT available on the server + // Note: These two are NOT available on the server float pitch = 0.0f; float yaw = 0.0f; float movement_speed = 0.0f; diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index cfb7484df..73240ce9b 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -260,12 +260,13 @@ int LuaLocalPlayer::l_get_control(lua_State *L) set("zoom", c.zoom); set("dig", c.dig); set("place", c.place); - // Player movement in polar coordinates and non-binary speed - lua_pushnumber(L, c.movement_speed); - lua_setfield(L, -2, "movement_speed"); - lua_pushnumber(L, c.movement_direction); - lua_setfield(L, -2, "movement_direction"); - // Provide direction keys to ensure compatibility + + v2f movement = c.getMovement(); + lua_pushnumber(L, movement.X); + lua_setfield(L, -2, "movement_x"); + lua_pushnumber(L, movement.Y); + lua_setfield(L, -2, "movement_y"); + set("up", c.direction_keys & (1 << 0)); set("down", c.direction_keys & (1 << 1)); set("left", c.direction_keys & (1 << 2)); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index ebd1bde71..1a732d9c6 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1622,6 +1622,13 @@ int ObjectRef::l_get_player_control(lua_State *L) lua_setfield(L, -2, "dig"); lua_pushboolean(L, control.place); lua_setfield(L, -2, "place"); + + v2f movement = control.getMovement(); + lua_pushnumber(L, movement.X); + lua_setfield(L, -2, "movement_x"); + lua_pushnumber(L, movement.Y); + lua_setfield(L, -2, "movement_y"); + // Legacy fields to ensure mod compatibility lua_pushboolean(L, control.dig); lua_setfield(L, -2, "LMB"); From 3797ca52c4559da21623a39615c4e4d84d845ea9 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 2 Oct 2024 11:01:30 +0200 Subject: [PATCH 150/200] Network: offload often changed constants to source file (#15207) * Network: offload often changed constants to source file This prevents unnecessary recompiling when using incremental builds. There is also no need to have separate max proto version variables; as they're subject to the handshake between client and server. The code is also expected to support the same version (or higher). Co-authored-by: sfan5 --- src/client/client.cpp | 2 +- src/client/client.h | 1 + src/main.cpp | 2 +- src/network/CMakeLists.txt | 1 + src/network/networkpacket.h | 3 +- src/network/networkprotocol.cpp | 66 ++++++++ src/network/networkprotocol.h | 234 +--------------------------- src/network/serverpackethandler.cpp | 6 +- src/script/lua_api/l_mainmenu.cpp | 2 +- src/script/lua_api/l_util.cpp | 2 +- src/server.cpp | 6 +- src/server/clientiface.h | 1 + util/bump_version.sh | 7 +- 13 files changed, 92 insertions(+), 241 deletions(-) create mode 100644 src/network/networkprotocol.cpp diff --git a/src/client/client.cpp b/src/client/client.cpp index a7b714069..2cf22b328 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1147,7 +1147,7 @@ void Client::sendInit(const std::string &playerName) NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size())); pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) 0; - pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX; + pkt << CLIENT_PROTOCOL_VERSION_MIN << LATEST_PROTOCOL_VERSION; pkt << playerName; Send(&pkt); diff --git a/src/client/client.h b/src/client/client.h index f9f77ede4..0b26ff94d 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gameparams.h" #include "script/common/c_types.h" // LuaError #include "util/numeric.h" +#include "util/string.h" // StringMap #ifdef SERVER #error Do not include in server builds diff --git a/src/main.cpp b/src/main.cpp index 9f737b86d..1e717bfd1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -728,7 +728,7 @@ static void startup_message() print_version(infostream); infostream << "SER_FMT_VER_HIGHEST_READ=" << TOSTRING(SER_FMT_VER_HIGHEST_READ) << - " LATEST_PROTOCOL_VERSION=" << TOSTRING(LATEST_PROTOCOL_VERSION) + " LATEST_PROTOCOL_VERSION=" << LATEST_PROTOCOL_VERSION << std::endl; } diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 8f17e58af..6291e23af 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -4,6 +4,7 @@ set(common_network_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/mtp/impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mtp/threads.cpp ${CMAKE_CURRENT_SOURCE_DIR}/networkpacket.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/networkprotocol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serveropcodes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serverpackethandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/socket.cpp diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index ee85b2951..0260f8072 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -19,7 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "util/pointer.h" +#include "util/pointer.h" // Buffer +#include "irrlichttypes_bloated.h" #include "networkprotocol.h" #include diff --git a/src/network/networkprotocol.cpp b/src/network/networkprotocol.cpp new file mode 100644 index 000000000..350b4d734 --- /dev/null +++ b/src/network/networkprotocol.cpp @@ -0,0 +1,66 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "networkprotocol.h" + + +/* + PROTOCOL VERSION < 37: + Until (and including) version 0.4.17.1 + PROTOCOL VERSION 37: + Redo detached inventory sending + Add TOCLIENT_NODEMETA_CHANGED + New network float format + ContentFeatures version 13 + Add full Euler rotations instead of just yaw + Add TOCLIENT_PLAYER_SPEED + [bump for 5.0.0] + PROTOCOL VERSION 38: + Incremental inventory sending mode + Unknown inventory serialization fields no longer throw an error + Mod-specific formspec version + Player FOV override API + "ephemeral" added to TOCLIENT_PLAY_SOUND + PROTOCOL VERSION 39: + Updated set_sky packet + Adds new sun, moon and stars packets + Minimap modes + PROTOCOL VERSION 40: + TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added + PROTOCOL VERSION 41: + Added new particlespawner parameters + [scheduled bump for 5.6.0] + PROTOCOL VERSION 42: + TOSERVER_UPDATE_CLIENT_INFO added + new fields for TOCLIENT_SET_LIGHTING and TOCLIENT_SET_SKY + Send forgotten TweenedParameter properties + [scheduled bump for 5.7.0] + PROTOCOL VERSION 43: + "start_time" added to TOCLIENT_PLAY_SOUND + place_param2 type change u8 -> optional + [scheduled bump for 5.8.0] + PROTOCOL VERSION 44: + AO_CMD_SET_BONE_POSITION extended + Add TOCLIENT_MOVE_PLAYER_REL + Move default minimap from client-side C++ to server-side builtin Lua + [scheduled bump for 5.9.0] + PROTOCOL VERSION 45: + Minimap HUD element supports negative size values as percentages + [bump for 5.9.1] + PROTOCOL VERSION 46: + Move default hotbar from client-side C++ to server-side builtin Lua + Add shadow tint to Lighting packets + Add shadow color to CloudParam packets + Move death screen to server and make it a regular formspec + The server no longer triggers the hardcoded client-side death + formspec, but the client still supports it for compatibility with + old servers. + Rename TOCLIENT_DEATHSCREEN to TOCLIENT_DEATHSCREEN_LEGACY + Rename TOSERVER_RESPAWN to TOSERVER_RESPAWN_LEGACY + [scheduled bump for 5.10.0] +*/ + +const u16 LATEST_PROTOCOL_VERSION = 46; + +// See also formspec [Version History] in doc/lua_api.md +const u16 FORMSPEC_API_VERSION = 7; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 900492d05..4ee02209a 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -19,240 +19,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once -#include "util/string.h" +#include "irrTypes.h" +using namespace irr; -/* - changes by PROTOCOL_VERSION: - - PROTOCOL_VERSION 3: - Base for writing changes here - PROTOCOL_VERSION 4: - Add TOCLIENT_MEDIA - Add TOCLIENT_TOOLDEF - Add TOCLIENT_NODEDEF - Add TOCLIENT_CRAFTITEMDEF - Add TOSERVER_INTERACT - Obsolete TOSERVER_CLICK_ACTIVEOBJECT - Obsolete TOSERVER_GROUND_ACTION - PROTOCOL_VERSION 5: - Make players to be handled mostly as ActiveObjects - PROTOCOL_VERSION 6: - Only non-cached textures are sent - PROTOCOL_VERSION 7: - Add TOCLIENT_ITEMDEF - Obsolete TOCLIENT_TOOLDEF - Obsolete TOCLIENT_CRAFTITEMDEF - Compress the contents of TOCLIENT_ITEMDEF and TOCLIENT_NODEDEF - PROTOCOL_VERSION 8: - Digging based on item groups - Many things - PROTOCOL_VERSION 9: - ContentFeatures and NodeDefManager use a different serialization - format; better for future version cross-compatibility - Many things - Obsolete TOCLIENT_PLAYERITEM - PROTOCOL_VERSION 10: - TOCLIENT_PRIVILEGES - Version raised to force 'fly' and 'fast' privileges into effect. - Node metadata change (came in later; somewhat incompatible) - PROTOCOL_VERSION 11: - TileDef in ContentFeatures - Nodebox drawtype - (some dev snapshot) - TOCLIENT_INVENTORY_FORMSPEC - (0.4.0, 0.4.1) - PROTOCOL_VERSION 12: - TOSERVER_INVENTORY_FIELDS - 16-bit node ids - TOCLIENT_DETACHED_INVENTORY - PROTOCOL_VERSION 13: - InventoryList field "Width" (deserialization fails with old versions) - PROTOCOL_VERSION 14: - Added transfer of player pressed keys to the server - Added new messages for mesh and bone animation, as well as attachments - AO_CMD_SET_ANIMATION - AO_CMD_SET_BONE_POSITION - GENERIC_CMD_SET_ATTACHMENT - PROTOCOL_VERSION 15: - Serialization format changes - PROTOCOL_VERSION 16: - TOCLIENT_SHOW_FORMSPEC - PROTOCOL_VERSION 17: - Serialization format change: include backface_culling flag in TileDef - Added rightclickable field in nodedef - TOCLIENT_SPAWN_PARTICLE - TOCLIENT_ADD_PARTICLESPAWNER - TOCLIENT_DELETE_PARTICLESPAWNER - PROTOCOL_VERSION 18: - damageGroups added to ToolCapabilities - sound_place added to ItemDefinition - PROTOCOL_VERSION 19: - AO_CMD_SET_PHYSICS_OVERRIDE - PROTOCOL_VERSION 20: - TOCLIENT_HUDADD - TOCLIENT_HUDRM - TOCLIENT_HUDCHANGE - TOCLIENT_HUD_SET_FLAGS - PROTOCOL_VERSION 21: - TOCLIENT_BREATH - TOSERVER_BREATH - range added to ItemDefinition - drowning, leveled and liquid_range added to ContentFeatures - stepheight and collideWithObjects added to object properties - version, heat and humidity transfer in MapBock - automatic_face_movement_dir and automatic_face_movement_dir_offset - added to object properties - PROTOCOL_VERSION 22: - add swap_node - PROTOCOL_VERSION 23: - Obsolete TOSERVER_RECEIVED_MEDIA - Server: Stop using TOSERVER_CLIENT_READY - PROTOCOL_VERSION 24: - ContentFeatures version 7 - ContentFeatures: change number of special tiles to 6 (CF_SPECIAL_COUNT) - PROTOCOL_VERSION 25: - Rename TOCLIENT_ACCESS_DENIED to TOCLIENT_ACCESS_DENIED_LEGAGY - Rename TOCLIENT_DELETE_PARTICLESPAWNER to - TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY - Rename TOSERVER_PASSWORD to TOSERVER_PASSWORD_LEGACY - Rename TOSERVER_INIT to TOSERVER_INIT_LEGACY - Rename TOCLIENT_INIT to TOCLIENT_INIT_LEGACY - Add TOCLIENT_ACCESS_DENIED new opcode (0x0A), using error codes - for standard error, keeping customisation possible. This - permit translation - Add TOCLIENT_DELETE_PARTICLESPAWNER (0x53), fixing the u16 read and - reading u32 - Add new opcode TOSERVER_INIT for client presentation to server - Add new opcodes TOSERVER_FIRST_SRP, TOSERVER_SRP_BYTES_A, - TOSERVER_SRP_BYTES_M, TOCLIENT_SRP_BYTES_S_B - for the three supported auth mechanisms around srp - Add new opcodes TOCLIENT_ACCEPT_SUDO_MODE and TOCLIENT_DENY_SUDO_MODE - for sudo mode handling (auth mech generic way of changing password). - Add TOCLIENT_HELLO for presenting server to client after client - presentation - Add TOCLIENT_AUTH_ACCEPT to accept connection from client - Rename GENERIC_CMD_SET_ATTACHMENT to AO_CMD_ATTACH_TO - PROTOCOL_VERSION 26: - Add TileDef tileable_horizontal, tileable_vertical flags - PROTOCOL_VERSION 27: - backface_culling: backwards compatibility for playing with - newer client on pre-27 servers. - Add nodedef v3 - connected nodeboxes - PROTOCOL_VERSION 28: - CPT2_MESHOPTIONS - PROTOCOL_VERSION 29: - Server doesn't accept TOSERVER_BREATH anymore - serialization of TileAnimation params changed - TAT_SHEET_2D - Removed client-sided chat perdiction - PROTOCOL VERSION 30: - New ContentFeatures serialization version - Add node and tile color and palette - Fix plantlike visual_scale being applied squared and add compatibility - with pre-30 clients by sending sqrt(visual_scale) - PROTOCOL VERSION 31: - Add tile overlay - Stop sending TOSERVER_CLIENT_READY - PROTOCOL VERSION 32: - Add fading sounds - PROTOCOL VERSION 33: - Add TOCLIENT_UPDATE_PLAYER_LIST and send the player list to the client, - instead of guessing based on the active object list. - PROTOCOL VERSION 34: - Add sound pitch - PROTOCOL VERSION 35: - Rename TOCLIENT_CHAT_MESSAGE to TOCLIENT_CHAT_MESSAGE_OLD (0x30) - Add TOCLIENT_CHAT_MESSAGE (0x2F) - This chat message is a signalisation message containing various - informations: - * timestamp - * sender - * type (RAW, NORMAL, ANNOUNCE, SYSTEM) - * content - Add TOCLIENT_CSM_RESTRICTION_FLAGS to define which CSM features should be - limited - Add settable player collisionbox. Breaks compatibility with older - clients as a 1-node vertical offset has been removed from player's - position - Add settable player stepheight using existing object property. - Breaks compatibility with older clients. - PROTOCOL VERSION 36: - Backwards compatibility drop - Add 'can_zoom' to player object properties - Add glow to object properties - Change TileDef serialization format. - Add world-aligned tiles. - Mod channels - Raise ObjectProperties version to 3 for removing 'can_zoom' and adding - 'zoom_fov'. - Nodebox version 5 - Add disconnected nodeboxes - Add TOCLIENT_FORMSPEC_PREPEND - PROTOCOL VERSION 37: - Redo detached inventory sending - Add TOCLIENT_NODEMETA_CHANGED - New network float format - ContentFeatures version 13 - Add full Euler rotations instead of just yaw - Add TOCLIENT_PLAYER_SPEED - PROTOCOL VERSION 38: - Incremental inventory sending mode - Unknown inventory serialization fields no longer throw an error - Mod-specific formspec version - Player FOV override API - "ephemeral" added to TOCLIENT_PLAY_SOUND - PROTOCOL VERSION 39: - Updated set_sky packet - Adds new sun, moon and stars packets - Minimap modes - PROTOCOL VERSION 40: - TOCLIENT_MEDIA_PUSH changed, TOSERVER_HAVE_MEDIA added - PROTOCOL VERSION 41: - Added new particlespawner parameters - [scheduled bump for 5.6.0] - PROTOCOL VERSION 42: - TOSERVER_UPDATE_CLIENT_INFO added - new fields for TOCLIENT_SET_LIGHTING and TOCLIENT_SET_SKY - Send forgotten TweenedParameter properties - [scheduled bump for 5.7.0] - PROTOCOL VERSION 43: - "start_time" added to TOCLIENT_PLAY_SOUND - place_param2 type change u8 -> optional - [scheduled bump for 5.8.0] - PROTOCOL VERSION 44: - AO_CMD_SET_BONE_POSITION extended - Add TOCLIENT_MOVE_PLAYER_REL - Move default minimap from client-side C++ to server-side builtin Lua - [scheduled bump for 5.9.0] - PROTOCOL VERSION 45: - Minimap HUD element supports negative size values as percentages - [bump for 5.9.1] - PROTOCOL VERSION 46: - Move default hotbar from client-side C++ to server-side builtin Lua - Add shadow tint to Lighting packets - Add shadow color to CloudParam packets - Move death screen to server and make it a regular formspec - The server no longer triggers the hardcoded client-side death - formspec, but the client still supports it for compatibility with - old servers. - Rename TOCLIENT_DEATHSCREEN to TOCLIENT_DEATHSCREEN_LEGACY - Rename TOSERVER_RESPAWN to TOSERVER_RESPAWN_LEGACY - [scheduled bump for 5.10.0] -*/ - -#define LATEST_PROTOCOL_VERSION 46 -#define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) +extern const u16 LATEST_PROTOCOL_VERSION; // Server's supported network protocol range -#define SERVER_PROTOCOL_VERSION_MIN 37 -#define SERVER_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION +constexpr u16 SERVER_PROTOCOL_VERSION_MIN = 37; // Client's supported network protocol range -#define CLIENT_PROTOCOL_VERSION_MIN 37 -#define CLIENT_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION +constexpr u16 CLIENT_PROTOCOL_VERSION_MIN = 37; -// See also formspec [Version History] in doc/lua_api.md -#define FORMSPEC_API_VERSION 7 +extern const u16 FORMSPEC_API_VERSION; #define TEXTURENAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-" diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 3e60e8cc8..d1e1ddacb 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -135,10 +135,10 @@ void Server::handleCommand_Init(NetworkPacket* pkt) // Figure out a working version if it is possible at all if (max_net_proto_version >= SERVER_PROTOCOL_VERSION_MIN || - min_net_proto_version <= SERVER_PROTOCOL_VERSION_MAX) { + min_net_proto_version <= LATEST_PROTOCOL_VERSION) { // If maximum is larger than our maximum, go with our maximum - if (max_net_proto_version > SERVER_PROTOCOL_VERSION_MAX) - net_proto_version = SERVER_PROTOCOL_VERSION_MAX; + if (max_net_proto_version > LATEST_PROTOCOL_VERSION) + net_proto_version = LATEST_PROTOCOL_VERSION; // Else go with client's maximum else net_proto_version = max_net_proto_version; diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index bf20f14ba..65e69d7e4 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -1034,7 +1034,7 @@ int ModApiMainMenu::l_get_min_supp_proto(lua_State *L) int ModApiMainMenu::l_get_max_supp_proto(lua_State *L) { - lua_pushinteger(L, CLIENT_PROTOCOL_VERSION_MAX); + lua_pushinteger(L, LATEST_PROTOCOL_VERSION); return 1; } diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 75a11a050..45a447cb3 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -531,7 +531,7 @@ int ModApiUtil::l_get_version(lua_State *L) lua_pushnumber(L, SERVER_PROTOCOL_VERSION_MIN); lua_setfield(L, table, "proto_min"); - lua_pushnumber(L, SERVER_PROTOCOL_VERSION_MAX); + lua_pushnumber(L, LATEST_PROTOCOL_VERSION); lua_setfield(L, table, "proto_max"); if (strcmp(g_version_string, g_version_hash) != 0) { diff --git a/src/server.cpp b/src/server.cpp index e57d81dbb..e4fecf7c1 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -4292,12 +4292,10 @@ u16 Server::getProtocolVersionMin() min_proto = LATEST_PROTOCOL_VERSION; return rangelim(min_proto, SERVER_PROTOCOL_VERSION_MIN, - SERVER_PROTOCOL_VERSION_MAX); + LATEST_PROTOCOL_VERSION); } u16 Server::getProtocolVersionMax() { - return g_settings->getBool("strict_protocol_version_checking") - ? LATEST_PROTOCOL_VERSION - : SERVER_PROTOCOL_VERSION_MAX; + return LATEST_PROTOCOL_VERSION; } diff --git a/src/server/clientiface.h b/src/server/clientiface.h index 3f5ba6434..5b20d8f70 100644 --- a/src/server/clientiface.h +++ b/src/server/clientiface.h @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include #include #include #include diff --git a/util/bump_version.sh b/util/bump_version.sh index 699bbcf77..5e920dc70 100755 --- a/util/bump_version.sh +++ b/util/bump_version.sh @@ -43,7 +43,12 @@ read_versions() { # in: $1 read_proto_ver() { local ref=$1 - git show "$ref":src/network/networkprotocol.h | grep -oE 'LATEST_PROTOCOL_VERSION [0-9]+' | tr -dC 0-9 + local output=$(git show "$ref":src/network/networkprotocol.cpp 2>/dev/null) + if [ -z "$output" ]; then + # Fallback to previous file (for tags < 5.10.0) + output=$(git show "$ref":src/network/networkprotocol.h) + fi + grep -oE 'LATEST_PROTOCOL_VERSION\s+=?\s*[0-9]+' <<<"$output" | tr -dC 0-9 } ## Prompts for new version From eefaef53b71730d94d0027568053679939ccca8a Mon Sep 17 00:00:00 2001 From: grorp Date: Thu, 3 Oct 2024 11:36:48 +0200 Subject: [PATCH 151/200] Fix hypertext action firing twice on touchscreen (#15217) --- src/gui/guiHyperText.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index 6f30ac8ce..44019ebe2 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -1146,7 +1146,7 @@ bool GUIHyperText::OnEvent(const SEvent &event) } } - break; + return true; } } } From 132e43346e1029253e1f3e04ab445cd053e3c226 Mon Sep 17 00:00:00 2001 From: grorp Date: Thu, 3 Oct 2024 11:37:04 +0200 Subject: [PATCH 152/200] Setting structure improvements (#15218) --- builtin/mainmenu/settings/components.lua | 13 ++++ builtin/mainmenu/settings/dlg_settings.lua | 12 +++- builtin/settingtypes.txt | 75 +++++++++++----------- 3 files changed, 60 insertions(+), 40 deletions(-) diff --git a/builtin/mainmenu/settings/components.lua b/builtin/mainmenu/settings/components.lua index bfe64285c..79253558b 100644 --- a/builtin/mainmenu/settings/components.lua +++ b/builtin/mainmenu/settings/components.lua @@ -67,6 +67,19 @@ function make.heading(text) end +function make.note(text) + return { + full_width = true, + get_formspec = function(self, avail_w) + -- Assuming label height 0.4: + -- Position at y=0 to eat 0.2 of the padding above, leave 0.05. + -- The returned used_height doesn't include padding. + return ("label[0,0;%s]"):format(core.colorize("#bbb", core.formspec_escape(text))), 0.2 + end, + } +end + + --- Used for string and numeric style fields --- --- @param converter Function to coerce values from strings. diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 75f99376d..4842b2e1a 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -152,9 +152,19 @@ local function load() table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) do - local content = page_by_id.graphics_and_audio_shaders.content + local content = page_by_id.graphics_and_audio_effects.content local idx = table.indexof(content, "enable_dynamic_shadows") table.insert(content, idx, shadows_component) + + idx = table.indexof(content, "enable_auto_exposure") + 1 + local note = component_funcs.note(fgettext_ne("(The game will need to enable automatic exposure as well)")) + note.requires = get_setting_info("enable_auto_exposure").requires + table.insert(content, idx, note) + + idx = table.indexof(content, "enable_volumetric_lighting") + 1 + note = component_funcs.note(fgettext_ne("(The game will need to enable volumetric lighting as well)")) + note.requires = get_setting_info("enable_volumetric_lighting").requires + table.insert(content, idx, note) end -- These must not be translated, as they need to show in the local diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index cffc728d1..342fc24a6 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -262,31 +262,6 @@ viewing_range (Viewing range) int 190 20 4000 # Higher values result in a less detailed image. undersampling (Undersampling) int 1 1 8 -[**Graphical Effects] - -# Allows liquids to be translucent. -translucent_liquids (Translucent liquids) bool true - -# Leaves style: -# - Fancy: all faces visible -# - Simple: only outer faces, if defined special_tiles are used -# - Opaque: disable transparency -leaves_style (Leaves style) enum fancy fancy,simple,opaque - -# Connects glass if supported by node. -connected_glass (Connect glass) bool false - -# Enable smooth lighting with simple ambient occlusion. -# Disable for speed or for different looks. -smooth_lighting (Smooth lighting) bool true - -# Enables tradeoffs that reduce CPU load or increase rendering performance -# at the expense of minor visual glitches that do not impact game playability. -performance_tradeoffs (Tradeoffs for performance) bool false - -# Adds particles when digging a node. -enable_particles (Digging particles) bool true - [**3D] # 3D support. @@ -466,7 +441,29 @@ enable_raytraced_culling (Enable Raytraced Culling) bool true -[*Shaders] +[*Effects] + +# Allows liquids to be translucent. +translucent_liquids (Translucent liquids) bool true + +# Leaves style: +# - Fancy: all faces visible +# - Simple: only outer faces +# - Opaque: disable transparency +leaves_style (Leaves style) enum fancy fancy,simple,opaque + +# Connects glass if supported by node. +connected_glass (Connect glass) bool false + +# Enable smooth lighting with simple ambient occlusion. +smooth_lighting (Smooth lighting) bool true + +# Enables tradeoffs that reduce CPU load or increase rendering performance +# at the expense of minor visual glitches that do not impact game playability. +performance_tradeoffs (Tradeoffs for performance) bool false + +# Adds particles when digging a node. +enable_particles (Digging particles) bool true [**Waving Nodes] @@ -504,11 +501,6 @@ water_wave_length (Waving liquids wavelength) float 20.0 0.1 # Requires: shaders, enable_waving_water water_wave_speed (Waving liquids wave speed) float 5.0 -# When enabled, liquid reflections are simulated. -# -# Requires: shaders, enable_waving_water, enable_dynamic_shadows -enable_water_reflections (Liquid reflections) bool false - [**Dynamic shadows] # Set to true to enable Shadow Mapping. @@ -632,14 +624,6 @@ debanding (Enable Debanding) bool true # Requires: shaders, enable_post_processing enable_bloom (Enable Bloom) bool false -# Set to true to render debugging breakdown of the bloom effect. -# In debug mode, the screen is split into 4 quadrants: -# top-left - processed base image, top-right - final image -# bottom-left - raw base image, bottom-right - bloom texture. -# -# Requires: shaders, enable_post_processing, enable_bloom -enable_bloom_debug (Enable Bloom Debug) bool false - # Defines how much bloom is applied to the rendered image # Smaller values make bloom more subtle # Range: from 0.01 to 1.0, default: 0.05 @@ -677,6 +661,11 @@ enable_translucent_foliage (Translucent foliage) bool false # Requires: shaders, enable_dynamic_shadows enable_node_specular (Node specular) bool false +# When enabled, liquid reflections are simulated. +# +# Requires: shaders, enable_waving_water, enable_dynamic_shadows +enable_water_reflections (Liquid reflections) bool false + [*Audio] # Volume of all sounds. @@ -1940,6 +1929,14 @@ client_mesh_chunk (Client Mesh Chunksize) int 1 1 16 # Enables debug and error-checking in the OpenGL driver. opengl_debug (OpenGL debug) bool false +# Set to true to render debugging breakdown of the bloom effect. +# In debug mode, the screen is split into 4 quadrants: +# top-left - processed base image, top-right - final image +# bottom-left - raw base image, bottom-right - bloom texture. +# +# Requires: shaders, enable_post_processing, enable_bloom +enable_bloom_debug (Enable Bloom Debug) bool false + [**Sound] # Comma-separated list of AL and ALC extensions that should not be used. # Useful for testing. See al_extensions.[h,cpp] for details. From 3eef1ca28f05b13adf71b2486d2650ef125239df Mon Sep 17 00:00:00 2001 From: grorp Date: Thu, 3 Oct 2024 11:37:14 +0200 Subject: [PATCH 153/200] Fix incorrect SMaterial::operator!= (regression from #15165) (#15226) --- irr/include/SMaterial.h | 1 + 1 file changed, 1 insertion(+) diff --git a/irr/include/SMaterial.h b/irr/include/SMaterial.h index cceccad78..8c0a51dfd 100644 --- a/irr/include/SMaterial.h +++ b/irr/include/SMaterial.h @@ -410,6 +410,7 @@ public: { bool different = MaterialType != b.MaterialType || + ColorParam != b.ColorParam || MaterialTypeParam != b.MaterialTypeParam || Thickness != b.Thickness || Wireframe != b.Wireframe || From 3397950a0e34b1d89af758610a3d099100e6acf7 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Fri, 4 Oct 2024 10:42:09 +0200 Subject: [PATCH 154/200] Clarify bit meaning in param2 palette (#15225) --- doc/lua_api.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 4bf7d31c1..aeb903ae2 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -1394,16 +1394,19 @@ The function of `param2` is determined by `paramtype2` in node definition. The palette should have 256 pixels. * `paramtype2 = "colorfacedir"` * Same as `facedir`, but with colors. - * The first three bits of `param2` tells which color is picked from the + * The three most significant bits of `param2` tells which color is picked from the palette. The palette should have 8 pixels. + * The five least significant bits contain the `facedir` value. * `paramtype2 = "color4dir"` - * Same as `facedir`, but with colors. - * The first six bits of `param2` tells which color is picked from the + * Same as `4dir`, but with colors. + * The six most significant bits of `param2` tells which color is picked from the palette. The palette should have 64 pixels. + * The two least significant bits contain the `4dir` rotation. * `paramtype2 = "colorwallmounted"` * Same as `wallmounted`, but with colors. - * The first five bits of `param2` tells which color is picked from the + * The five most significant bits of `param2` tells which color is picked from the palette. The palette should have 32 pixels. + * The three least significant bits contain the `wallmounted` value. * `paramtype2 = "glasslikeliquidlevel"` * Only valid for "glasslike_framed" or "glasslike_framed_optional" drawtypes. "glasslike_framed_optional" nodes are only affected if the @@ -1417,9 +1420,9 @@ The function of `param2` is determined by `paramtype2` in node definition. * Liquid texture is defined using `special_tiles = {"modname_tilename.png"}` * `paramtype2 = "colordegrotate"` * Same as `degrotate`, but with colors. - * The first (most-significant) three bits of `param2` tells which color - is picked from the palette. The palette should have 8 pixels. - * Remaining 5 bits store rotation in range 0–23 (i.e. in 15° steps) + * The three most significant bits of `param2` tells which color is picked + from the palette. The palette should have 8 pixels. + * The five least significant bits store rotation in range 0–23 (i.e. in 15° steps) * `paramtype2 = "none"` * `param2` will not be used by the engine and can be used to store an arbitrary value From 57ca92e0eb880ae456df4a2617d4062bd34507c5 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Fri, 4 Oct 2024 10:42:25 +0200 Subject: [PATCH 155/200] Simplify minetest.strip_param2_color --- builtin/common/item_s.lua | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/builtin/common/item_s.lua b/builtin/common/item_s.lua index 72a722ed1..673c83877 100644 --- a/builtin/common/item_s.lua +++ b/builtin/common/item_s.lua @@ -166,20 +166,19 @@ function core.is_colored_paramtype(ptype) end function core.strip_param2_color(param2, paramtype2) - if not core.is_colored_paramtype(paramtype2) then + if paramtype2 == "color" then + return param2 + elseif paramtype2 == "colorfacedir" then + return math.floor(param2 / 32) * 32 + elseif paramtype2 == "color4dir" then + return math.floor(param2 / 4) * 4 + elseif paramtype2 == "colorwallmounted" then + return math.floor(param2 / 8) * 8 + elseif paramtype2 == "colordegrotate" then + return math.floor(param2 / 32) * 32 + else return nil end - if paramtype2 == "colorfacedir" then - param2 = math.floor(param2 / 32) * 32 - elseif paramtype2 == "color4dir" then - param2 = math.floor(param2 / 4) * 4 - elseif paramtype2 == "colorwallmounted" then - param2 = math.floor(param2 / 8) * 8 - elseif paramtype2 == "colordegrotate" then - param2 = math.floor(param2 / 32) * 32 - end - -- paramtype2 == "color" requires no modification. - return param2 end -- Content ID caching From a19d0033bc13528511ebe704af1c43733abfa790 Mon Sep 17 00:00:00 2001 From: sfence Date: Fri, 4 Oct 2024 10:42:37 +0200 Subject: [PATCH 156/200] Add forgotten lua_pop --- src/script/lua_api/l_object.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 1a732d9c6..a11308a2e 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2626,6 +2626,7 @@ int ObjectRef::l_set_lighting(lua_State *L) getfloatfield(L, -1, "intensity", lighting.shadow_intensity); lua_getfield(L, -1, "tint"); read_color(L, -1, &lighting.shadow_tint); + lua_pop(L, 1); // tint } lua_pop(L, 1); // shadows From 95d7348a08f3e2e0ca4bd75d7e9121a9c74de507 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Fri, 4 Oct 2024 10:44:03 +0200 Subject: [PATCH 157/200] Client: upscale [mask or base image (#15205) This improves texture pack compatibility. Masks are expected to be of the same size as the base texture. This change upscales the smaller texture if needed. The behaviour is now the same as a.png^b.png and a.png^[overlay:b.png (to mention a few). --- doc/lua_api.md | 18 ++++++++++++++++-- games/devtest/mods/testnodes/textures.lua | 6 ++++++ .../textures/testnodes_128x128_rgb.png | Bin 0 -> 1870 bytes .../textures/testnodes_mask_WRGBKW.png | Bin 0 -> 148 bytes src/client/imagesource.cpp | 2 ++ 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 games/devtest/mods/testnodes/textures/testnodes_128x128_rgb.png create mode 100644 games/devtest/mods/testnodes/textures/testnodes_mask_WRGBKW.png diff --git a/doc/lua_api.md b/doc/lua_api.md index aeb903ae2..57c99ef9d 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -490,6 +490,11 @@ to let the client generate textures on-the-fly. The modifiers are applied directly in sRGB colorspace, i.e. without gamma-correction. +### Notes + + * `TEXMOD_UPSCALE`: The texture with the lower resolution will be automatically + upscaled to the higher resolution texture. + ### Texture overlaying Textures can be overlaid by putting a `^` between them. @@ -503,8 +508,9 @@ Example: default_dirt.png^default_grass_side.png `default_grass_side.png` is overlaid over `default_dirt.png`. -The texture with the lower resolution will be automatically upscaled to -the higher resolution texture. + +*See notes: `TEXMOD_UPSCALE`* + ### Texture grouping @@ -701,6 +707,8 @@ Apply a mask to the base image. The mask is applied using binary AND. +*See notes: `TEXMOD_UPSCALE`* + #### `[sheet:x:,` Retrieves a tile at position x, y (in tiles, 0-indexed) @@ -798,6 +806,8 @@ in GIMP. Overlay is the same as Hard light but with the role of the two textures swapped, see the `[hardlight` modifier description for more detail about these blend modes. +*See notes: `TEXMOD_UPSCALE`* + #### `[hardlight:` Applies a Hard light blend with the two textures, like the Hard light layer @@ -813,6 +823,8 @@ increase contrast without clipping. Hard light is the same as Overlay but with the roles of the two textures swapped, i.e. `A.png^[hardlight:B.png` is the same as `B.png^[overlay:A.png` +*See notes: `TEXMOD_UPSCALE`* + #### `[png:` Embed a base64 encoded PNG image in the texture string. @@ -831,6 +843,8 @@ In particular consider `minetest.dynamic_add_media` and test whether using other texture modifiers could result in a shorter string than embedding a whole image, this may vary by use case. +*See notes: `TEXMOD_UPSCALE`* + Hardware coloring ----------------- diff --git a/games/devtest/mods/testnodes/textures.lua b/games/devtest/mods/testnodes/textures.lua index 96f291d6a..b95fbd62e 100644 --- a/games/devtest/mods/testnodes/textures.lua +++ b/games/devtest/mods/testnodes/textures.lua @@ -52,6 +52,12 @@ minetest.register_node("testnodes:fill_positioning_reference", { groups = {dig_immediate = 3}, }) +minetest.register_node("testnodes:modifier_mask", { + description = S("[mask Modifier Test Node"), + tiles = {"testnodes_128x128_rgb.png^[mask:testnodes_mask_WRGBKW.png"}, + groups = {dig_immediate = 3}, +}) + -- Node texture transparency test local alphas = { 64, 128, 191 } diff --git a/games/devtest/mods/testnodes/textures/testnodes_128x128_rgb.png b/games/devtest/mods/testnodes/textures/testnodes_128x128_rgb.png new file mode 100644 index 0000000000000000000000000000000000000000..060d8e67afc011f33683d706c56a1f13466ca67d GIT binary patch literal 1870 zcmV-U2eJ5xP)p|yj{wfht=m6#=n24OG&2Gu z{D4#yo0%0zL%%vz#e?s=%fB6ijN{c{oBh(OEdpjH25Rq?pZ9=wr>P{=#Kz>#4u>+rNp?IiGhZW{%8b@CI(btN)XVO7)a4CR)Tg+|b_AW(kwZDCSTe!~;LFRJiJ~vSn&oIqQ#;rt&0ri@;5F{Ou zNcHFgsUu;zRF%$}Fr8H}PC%wh8)783pzM5Y9Pday0%}uLoW>?XKwhefH^Pn^5V9sh zK#CY>HbI6jIvN>3&$eekiv!ww{mJozHdzRo z2LYIAGesm10ht2aRco`+;!&HbE{$}tIlcq5fZLgKd}*-3V=iREE z(Su*asK4VYBZgrdKx4gLjfOD>0jSoxN2J~e(CGC;iJ>0?NVDUQBZeCYh)?4_T)i9a zlLDNbm>BLM0Ml2+{IU9~Zeln*fXt%LoN3$^zyV#=vEwJ4c9tozaW~BA$oo8~X?xa8 z+&+h(%%acKG;YF+dW<2VIH{dsMzgw(;^KCsBWYp~CSjV6Y%9W%C=?{qP*x?roH>cj zg}!wZD!gXAwY)^V~=Xy|Pxtd3%n3_`6y9l6(-mO%os<$`* zm)%ZPp$-Ad>!mb|Vg%4~c2Qy|K)?$3Er=l(0n4=;5kndQ%j=sGL+m|Zh5HV~@Ock7 z%W!1RwV0fAbriWQBWZEEn7!O2Qu^u60^GIt(vITdHusBc_Fo&h$|B&Stg$jyP`bi% zMP^XaO5O@HNpB-idZ*Tz40+}#rxa>?c-ZFWgZXM-!;TmV* zfB@zVKN}$|f9&S<253+BHw648Mh5WJzRuvmMt&Z;BjEZNmbXefe&B`IN5Ii+=zU~a zb+`{#H3SiGgu*hZa9@moMGUgxKCH&Y5wM6sFx-djT{v0LUPQ5*MW1z&mUwlkTt{Wq zv;_$8pg?j(pICKOyt))6!abH&)4nUG%d!zEv*`1Vq*YX1dS{Y%c1L7VWwl)CZ4CmF zscL4?XO3~VGUXi^`Hsj-G2>;m!P47$1gun5q@Z>~KydL(7g|b_xQWOH@CDAEgj}v( z)0>y-vP(#j(RGQhG_CFkqXnrXRh=t2Pg{a5bzbmKs~0dH$}kA#L#J`b`JzhMGVx*@%tlS24W!WJQkf1pqLnF*h*FX z5fBu&uM&4s7fYVGqR&f53DqobWz#=xAi0y#=_aEPz(u6UglSWLHnBp|zi^*a&610! zdYW@`C!@cwk43<^h(uaei42Kx7!u|%y&p|%$xPz+wfU3zEI1}rO-|;HYk!_*r)b?u zs{{ePQdNBm1b-0FVRxH?n@FUpp0G@+9KV$ut&$i>^P2ot^8=93Fto24nhyaNVxSove|iM$Z_(y3)8D3c2r6Cu zG;=qiG=*2o#*w*IdNo!#1XOQ} zlYf)+?&e!WvQ_n(cj=V5ReUwpi4ah*vAmgQd%*1h1ougg?4?f3J~jDrTj>@#HHQr` zvo_OD!nclBga=x3`!8tY3LOa!*B!)7%PRd$oy8FlFS48CIEvem`p%rv&*Zrq z0_Hnxmk{*&h;dv6C-F13Bf^~OaIY#=ZA!uu4}G^(wOt7Mwxdz1+Jb}$zVv9v0W0PV zBL=De6Mn?dw;%?Qr3#*V2oS>!1keK=U!E9h5wM0{h8TYS2THf<#Ze@*iU0rr07*qo IM6N<$f(DOnVE_OC literal 0 HcmV?d00001 diff --git a/games/devtest/mods/testnodes/textures/testnodes_mask_WRGBKW.png b/games/devtest/mods/testnodes/textures/testnodes_mask_WRGBKW.png new file mode 100644 index 0000000000000000000000000000000000000000..03ab71e3fbcc32f2cfb00d253b45f7d53788d5cb GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPHV5AX?b1=0)*|G|*q|Ns9v6}uM# z1sptG978ywlM5P|*u;|!3K9#-f|!^@h1t?Y9Tf_gj6`H5BqCZIc*NM)*bSN)SF{Q+ muaj6No2PJwYsXT9bOwfhwW5w)BBrlErhB^jxvXgetDimension()); img->drop(); From 84b932197739e2ad1fdb5bd0ee1e167011086744 Mon Sep 17 00:00:00 2001 From: sfence Date: Fri, 4 Oct 2024 10:44:14 +0200 Subject: [PATCH 158/200] Switch to macOS 13, because brew support for macOS 12 gone (#15232) --- .github/workflows/macos.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index e193c828d..731d7d719 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -29,8 +29,8 @@ on: jobs: build: - # use lowest possible macOS running on x86_64 to support more users - runs-on: macos-12 + # use lowest possible macOS running on x86_64 supported by brew to support more users + runs-on: macos-13 steps: - uses: actions/checkout@v4 - name: Install deps From 05cbd84ae02aff94f1845b84301d7200123f9330 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Fri, 4 Oct 2024 04:45:09 -0400 Subject: [PATCH 159/200] Fix irrString use-after-free with char-like assignment (operator=) --- irr/include/irrString.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/irr/include/irrString.h b/irr/include/irrString.h index 9d9b288d8..76e0548d3 100644 --- a/irr/include/irrString.h +++ b/irr/include/irrString.h @@ -173,13 +173,24 @@ public: return *this; } - // no longer allowed! - _IRR_DEBUG_BREAK_IF((void *)c == (void *)c_str()); + if constexpr (sizeof(T) != sizeof(B)) { + _IRR_DEBUG_BREAK_IF( + (uintptr_t)c >= (uintptr_t)(str.data()) && + (uintptr_t)c < (uintptr_t)(str.data() + str.size())); + } + + if ((void *)c == (void *)c_str()) + return *this; u32 len = calclen(c); - str.resize(len); + // In case `c` is a pointer to our own buffer, we may not resize first + // or it can become invalid. + if (len > str.size()) + str.resize(len); for (u32 l = 0; l < len; ++l) - str[l] = (T)c[l]; + str[l] = static_cast(c[l]); + if (len < str.size()) + str.resize(len); return *this; } From 78aab8c95d4f7cf1ed32b0323ec2b5eeb0743488 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sun, 31 Mar 2024 19:24:27 +0100 Subject: [PATCH 160/200] ContentDB redesign: Add package dialog Co-authored-by: Gregor Parzefall --- builtin/common/misc_helpers.lua | 10 + builtin/mainmenu/content/contentdb.lua | 80 ++++- builtin/mainmenu/content/dlg_contentdb.lua | 114 +------- builtin/mainmenu/content/dlg_install.lua | 42 +++ builtin/mainmenu/content/dlg_package.lua | 325 +++++++++++++++++++++ builtin/mainmenu/content/init.lua | 1 + builtin/mainmenu/content/screenshots.lua | 39 ++- builtin/mainmenu/tab_about.lua | 7 +- doc/lua_api.md | 3 + doc/menu_lua_api.md | 7 +- src/script/lua_api/l_mainmenu.cpp | 27 ++ src/script/lua_api/l_mainmenu.h | 4 + 12 files changed, 529 insertions(+), 130 deletions(-) create mode 100644 builtin/mainmenu/content/dlg_package.lua diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 9c25b826f..2ad9b10af 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -235,6 +235,16 @@ function core.formspec_escape(text) end +local hypertext_escapes = { + ["\\"] = "\\\\", + ["<"] = "\\<", + [">"] = "\\>", +} +function core.hypertext_escape(text) + return text and text:gsub("[\\<>]", hypertext_escapes) +end + + function core.wrap_text(text, max_length, as_table) local result = {} local line = {} diff --git a/builtin/mainmenu/content/contentdb.lua b/builtin/mainmenu/content/contentdb.lua index e0479cb4c..4d59826dd 100644 --- a/builtin/mainmenu/content/contentdb.lua +++ b/builtin/mainmenu/content/contentdb.lua @@ -182,6 +182,23 @@ function contentdb.get_package_by_id(id) end +function contentdb.calculate_package_id(type, author, name) + local id = author:lower() .. "/" + if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then + id = id .. name:sub(1, #name - 5) + else + id = id .. name + end + return id +end + + +function contentdb.get_package_by_info(author, name) + local id = contentdb.calculate_package_id(nil, author, name) + return contentdb.package_by_id[id] +end + + -- Create a coroutine from `fn` and provide results to `callback` when complete (dead). -- Returns a resumer function. local function make_callback_coroutine(fn, callback) @@ -415,15 +432,7 @@ local function fetch_pkgs(params) local aliases = {} for _, package in pairs(packages) do - local name_len = #package.name - -- This must match what contentdb.update_paths() does! - package.id = package.author:lower() .. "/" - if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then - package.id = package.id .. package.name:sub(1, name_len - 5) - else - package.id = package.id .. package.name - end - + package.id = params.calculate_package_id(package.type, package.author, package.name) package.url_part = core.urlencode(package.author) .. "/" .. core.urlencode(package.name) if package.aliases then @@ -443,7 +452,7 @@ end function contentdb.fetch_pkgs(callback) contentdb.loading = true - core.handle_async(fetch_pkgs, nil, function(result) + core.handle_async(fetch_pkgs, { calculate_package_id = contentdb.calculate_package_id }, function(result) if result then contentdb.load_ok = true contentdb.load_error = false @@ -581,3 +590,54 @@ function contentdb.filter_packages(query, by_type) end end end + + +function contentdb.get_full_package_info(package, callback) + assert(package) + if package.full_info then + callback(package.full_info) + return + end + + local function fetch(params) + local version = core.get_version() + local base_url = core.settings:get("contentdb_url") + + local languages + local current_language = core.get_language() + if current_language ~= "" then + languages = { current_language, "en;q=0.8" } + else + languages = { "en" } + end + + local url = base_url .. + "/api/packages/" .. params.package.url_part .. "/for-client/?" .. + "protocol_version=" .. core.urlencode(core.get_max_supp_proto()) .. + "&engine_version=" .. core.urlencode(version.string) .. + "&formspec_version=" .. core.urlencode(core.get_formspec_version()) .. + "&include_images=false" + local http = core.get_http_api() + local response = http.fetch_sync({ + url = url, + extra_headers = { + "Accept-Language: " .. table.concat(languages, ", ") + }, + }) + if not response.succeeded then + return nil + end + + return core.parse_json(response.data) + end + + local function my_callback(value) + package.full_info = value + callback(value) + end + + if not core.handle_async(fetch, { package = package }, my_callback) then + core.log("error", "ERROR: async event failed") + callback(nil) + end +end diff --git a/builtin/mainmenu/content/dlg_contentdb.lua b/builtin/mainmenu/content/dlg_contentdb.lua index bcc89f7cd..025430bfa 100644 --- a/builtin/mainmenu/content/dlg_contentdb.lua +++ b/builtin/mainmenu/content/dlg_contentdb.lua @@ -46,48 +46,6 @@ local filter_types_type = { } -local function install_or_update_package(this, package) - local install_parent - if package.type == "mod" then - install_parent = core.get_modpath() - elseif package.type == "game" then - install_parent = core.get_gamepath() - elseif package.type == "txp" then - install_parent = core.get_texturepath() - else - error("Unknown package type: " .. package.type) - end - - if package.queued or package.downloading then - return - end - - local function on_confirm() - local dlg = create_install_dialog(package) - dlg:set_parent(this) - this:hide() - dlg:show() - - dlg:load_deps() - end - - if package.type == "mod" and #pkgmgr.games == 0 then - local dlg = messagebox("install_game", - fgettext("You need to install a game before you can install a mod")) - dlg:set_parent(this) - this:hide() - dlg:show() - elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then - local dlg = create_confirm_overwrite(package, on_confirm) - dlg:set_parent(this) - this:hide() - dlg:show() - else - on_confirm() - end -end - - -- Resolves the package specification stored in auto_install_spec into an actual package. -- May only be called after the package list has been loaded successfully. local function resolve_auto_install_spec() @@ -291,7 +249,7 @@ local function get_formspec(dlgdata) -- image formspec[#formspec + 1] = "image[0,0;1.5,1;" - formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package)) + formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package, package.thumbnail, 1)) formspec[#formspec + 1] = "]" -- title @@ -301,52 +259,17 @@ local function get_formspec(dlgdata) core.colorize("#BFBFBF", " by " .. package.author)) formspec[#formspec + 1] = "]" - -- buttons - local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15 - - local second_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir) - local third_base = "image_button[-2.4,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir) - formspec[#formspec + 1] = "container[" - formspec[#formspec + 1] = W - 0.375*2 - formspec[#formspec + 1] = ",0.1]" - - if package.downloading then - formspec[#formspec + 1] = "animated_image[-1.7,-0.15;1,1;downloading;" - formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir) - formspec[#formspec + 1] = "cdb_downloading.png;3;400;]" - elseif package.queued then - formspec[#formspec + 1] = second_base - formspec[#formspec + 1] = "cdb_queued.png;queued;]" - elseif not package.path then - local elem_name = "install_" .. i .. ";" - formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]" - formspec[#formspec + 1] = second_base .. "cdb_add.png;" .. elem_name .. "]" - formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Install") .. tooltip_colors - else - if package.installed_release < package.release then - -- The install_ action also handles updating - local elem_name = "install_" .. i .. ";" - formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]" - formspec[#formspec + 1] = third_base .. "cdb_update.png;" .. elem_name .. "]" - formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Update") .. tooltip_colors - - description_width = description_width - 0.7 - 0.15 - end - - local elem_name = "uninstall_" .. i .. ";" - formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]" - formspec[#formspec + 1] = second_base .. "cdb_clear.png;" .. elem_name .. "]" - formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors - end - - local web_elem_name = "view_" .. i .. ";" - formspec[#formspec + 1] = "image_button[-0.7,0;0.7,0.7;" .. - core.formspec_escape(defaulttexturedir) .. "cdb_viewonline.png;" .. web_elem_name .. "]" - formspec[#formspec + 1] = "tooltip[" .. web_elem_name .. - fgettext("View more information in a web browser") .. tooltip_colors - formspec[#formspec + 1] = "container_end[]" + -- button + formspec[#formspec + 1] = "button[" + formspec[#formspec + 1] = W-0.375*2-2 + formspec[#formspec + 1] = ",0.1;2,0.7;view_" + formspec[#formspec + 1] = i + formspec[#formspec + 1] = ";" + formspec[#formspec + 1] = fgettext("View") + formspec[#formspec + 1] = "]" -- description + local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15 formspec[#formspec + 1] = "textarea[1.855,0.3;" formspec[#formspec + 1] = tostring(description_width) formspec[#formspec + 1] = ",0.8;;;" @@ -434,26 +357,13 @@ local function handle_submit(this, fields) local package = contentdb.packages[i] assert(package) - if fields["install_" .. i] then - install_or_update_package(this, package) - return true - end - - if fields["uninstall_" .. i] then - local dlg = create_delete_content_dlg(package) + if fields["view_" .. i] then + local dlg = create_package_dialog(package) dlg:set_parent(this) this:hide() dlg:show() return true end - - if fields["view_" .. i] then - local url = ("%s/packages/%s?protocol_version=%d"):format( - core.settings:get("contentdb_url"), package.url_part, - core.get_max_supp_proto()) - core.open_url(url) - return true - end end return false diff --git a/builtin/mainmenu/content/dlg_install.lua b/builtin/mainmenu/content/dlg_install.lua index 89819be2a..3f43bd23c 100644 --- a/builtin/mainmenu/content/dlg_install.lua +++ b/builtin/mainmenu/content/dlg_install.lua @@ -244,3 +244,45 @@ function create_install_dialog(package) return dlg end + + +function install_or_update_package(parent, package) + local install_parent + if package.type == "mod" then + install_parent = core.get_modpath() + elseif package.type == "game" then + install_parent = core.get_gamepath() + elseif package.type == "txp" then + install_parent = core.get_texturepath() + else + error("Unknown package type: " .. package.type) + end + + if package.queued or package.downloading then + return + end + + local function on_confirm() + local dlg = create_install_dialog(package) + dlg:set_parent(parent) + parent:hide() + dlg:show() + + dlg:load_deps() + end + + if package.type == "mod" and #pkgmgr.games == 0 then + local dlg = messagebox("install_game", + fgettext("You need to install a game before you can install a mod")) + dlg:set_parent(parent) + parent:hide() + dlg:show() + elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then + local dlg = create_confirm_overwrite(package, on_confirm) + dlg:set_parent(parent) + parent:hide() + dlg:show() + else + on_confirm() + end +end diff --git a/builtin/mainmenu/content/dlg_package.lua b/builtin/mainmenu/content/dlg_package.lua new file mode 100644 index 000000000..78bdf2e71 --- /dev/null +++ b/builtin/mainmenu/content/dlg_package.lua @@ -0,0 +1,325 @@ +--Minetest +--Copyright (C) 2018-24 rubenwardy +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +local function get_info_formspec(size, padding, text) + return table.concat({ + "formspec_version[6]", + "size[", size.x, ",", size.y, "]", + "padding[0,0]", + "bgcolor[;true]", + + "label[4,4.35;", text, "]", + "container[", padding.x, ",", size.y - 0.8 - padding.y, "]", + "button[0,0;2,0.8;back;", fgettext("Back"), "]", + "container_end[]", + }) +end + + +local function get_formspec(data) + -- Padding is increased on Android to account for notches + -- TODO: use Android API to determine size of cut outs + local window_padding = { x = PLATFORM == "Android" and 1 or 0.5, y = PLATFORM == "Android" and 0.25 or 0.5 } + local window = core.get_window_info() + local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y } + size.x = math.min(size.x, 20) + local W = size.x - window_padding.x * 2 + local H = size.y - window_padding.y * 2 + + if not data.info then + if not data.loading and not data.loading_error then + data.loading = true + + contentdb.get_full_package_info(data.package, function(info) + data.loading = false + + if info == nil then + data.loading_error = true + ui.update() + return + end + + if info.forums then + info.forums = "https://forum.minetest.net/viewtopic.php?t=" .. info.forums + end + + assert(data.package.name == info.name) + data.info = info + ui.update() + end) + end + + -- get_full_package_info can return cached info immediately, so + -- check to see if that happened + if not data.info then + if data.loading_error then + return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved")) + end + return get_info_formspec(size, window_padding, fgettext("Loading...")) + end + end + + -- Check installation status + contentdb.update_paths() + + local info = data.info + + local info_line = + fgettext("by $1 — $2 downloads — +$3 / $4 / -$5", + info.author, info.downloads, + info.reviews.positive, info.reviews.neutral, info.reviews.negative) + + local bottom_buttons_y = H - 0.8 + + local formspec = { + "formspec_version[7]", + "size[", size.x, ",", size.y, "]", + "padding[0,0]", + "bgcolor[;true]", + + "container[", window_padding.x, ",", window_padding.y, "]", + + "button[0,", bottom_buttons_y, ";2,0.8;back;", fgettext("Back"), "]", + "button[", W - 3, ",", bottom_buttons_y, ";3,0.8;open_contentdb;", fgettext("ContentDB page"), "]", + + "style_type[label;font_size=+24;font=bold]", + "label[0,0.4;", core.formspec_escape(info.title), "]", + "style_type[label;font_size=;font=]", + + "label[0,1.2;", core.formspec_escape(info_line), "]", + } + + table.insert_all(formspec, { + "container[", W - 6, ",0]" + }) + + local left_button_rect = "0,0;2.875,1" + local right_button_rect = "3.125,0;2.875,1" + if data.package.downloading then + formspec[#formspec + 1] = "animated_image[5,0;1,1;downloading;" + formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir) + formspec[#formspec + 1] = "cdb_downloading.png;3;400;]" + elseif data.package.queued then + formspec[#formspec + 1] = "style[queued;border=false]" + formspec[#formspec + 1] = "image_button[5,0;1,1;" .. core.formspec_escape(defaulttexturedir) + formspec[#formspec + 1] = "cdb_queued.png;queued;]" + elseif not data.package.path then + formspec[#formspec + 1] = "style[install;bgcolor=green]" + formspec[#formspec + 1] = "button[" + formspec[#formspec + 1] = right_button_rect + formspec[#formspec + 1] =";install;" + formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size) + formspec[#formspec + 1] = "]" + else + if data.package.installed_release < data.package.release then + -- The install_ action also handles updating + formspec[#formspec + 1] = "style[install;bgcolor=#28ccdf]" + formspec[#formspec + 1] = "button[" + formspec[#formspec + 1] = left_button_rect + formspec[#formspec + 1] = ";install;" + formspec[#formspec + 1] = fgettext("Update") + formspec[#formspec + 1] = "]" + end + + formspec[#formspec + 1] = "style[uninstall;bgcolor=#a93b3b]" + formspec[#formspec + 1] = "button[" + formspec[#formspec + 1] = right_button_rect + formspec[#formspec + 1] = ";uninstall;" + formspec[#formspec + 1] = fgettext("Uninstall") + formspec[#formspec + 1] = "]" + end + + local current_tab = data.current_tab or 1 + local tab_titles = { + fgettext("Description"), + fgettext("Information"), + } + + local tab_body_height = bottom_buttons_y - 2.8 + + table.insert_all(formspec, { + "container_end[]", + + "box[0,2.55;", W, ",", tab_body_height, ";#ffffff11]", + + "tabheader[0,2.55;", W, ",0.8;tabs;", + table.concat(tab_titles, ","), ";", current_tab, ";true;true]", + + "container[0,2.8]", + }) + + if current_tab == 1 then + -- Screenshots and description + local hypertext = "" .. core.hypertext_escape(info.short_description) .. "\n" + local winfo = core.get_window_info() + local fs_to_px = winfo.size.x / winfo.max_formspec_size.x + for i, ss in ipairs(info.screenshots) do + local path = get_screenshot(data.package, ss.url, 2) + hypertext = hypertext .. "" + if i ~= #info.screenshots then + hypertext = hypertext .. "" + end + end + hypertext = hypertext .. "\n" .. info.long_description.head + + local first = true + local function add_link_button(label, name) + if info[name] then + if not first then + hypertext = hypertext .. " | " + end + hypertext = hypertext .. "" .. core.hypertext_escape(label) .. "" + info.long_description.links["link_" .. name] = info[name] + first = false + end + end + + add_link_button(fgettext("Donate"), "donate_url") + add_link_button(fgettext("Website"), "website") + add_link_button(fgettext("Source"), "repo") + add_link_button(fgettext("Issue Tracker"), "issue_tracker") + add_link_button(fgettext("Translate"), "translation_url") + add_link_button(fgettext("Forum Topic"), "forums") + + hypertext = hypertext .. "\n\n" .. info.long_description.body + + hypertext = hypertext:gsub(""] = "\\>", - } - string = string:gsub("[\\<>]", hypertext_escapes) + string = core.hypertext_escape(string) string = string:gsub("%[.-%]", "%1") table.insert(dest, string) diff --git a/doc/lua_api.md b/doc/lua_api.md index 57c99ef9d..2fb2950d3 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6581,6 +6581,9 @@ Formspec * `minetest.formspec_escape(string)`: returns a string * escapes the characters "[", "]", "\", "," and ";", which cannot be used in formspecs. +* `minetest.hypertext_escape(string)`: returns a string + * escapes the characters "\", "<", and ">" to show text in a hypertext element. + * not safe for use with tag attributes. * `minetest.explode_table_event(string)`: returns a table * returns e.g. `{type="CHG", row=1, column=2}` * `type` is one of: diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index be63af904..c03c0501e 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -57,7 +57,10 @@ Functions * returns the maximum supported network protocol version * `core.open_url(url)` * opens the URL in a web browser, returns false on failure. - * Must begin with http:// or https:// + * `url` must begin with http:// or https:// +* `core.open_url_dialog(url)` + * shows a dialog to allow the user to choose whether to open a URL. + * `url` must begin with http:// or https:// * `core.open_dir(path)` * opens the path in the system file browser/explorer, returns false on failure. * Must be an existing directory. @@ -65,6 +68,8 @@ Functions * Android only. Shares file using the share popup * `core.get_version()` (possible in async calls) * returns current core version +* `core.get_formspec_version()` + * returns maximum supported formspec version diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 65e69d7e4..20faec9e0 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -41,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content/mod_configuration.h" #include "threading/mutex_auto_lock.h" #include "common/c_converter.h" +#include "gui/guiOpenURL.h" /******************************************************************************/ std::string ModApiMainMenu::getTextData(lua_State *L, const std::string &name) @@ -1038,6 +1039,13 @@ int ModApiMainMenu::l_get_max_supp_proto(lua_State *L) return 1; } +/******************************************************************************/ +int ModApiMainMenu::l_get_formspec_version(lua_State *L) +{ + lua_pushinteger(L, FORMSPEC_API_VERSION); + return 1; +} + /******************************************************************************/ int ModApiMainMenu::l_open_url(lua_State *L) { @@ -1046,6 +1054,22 @@ int ModApiMainMenu::l_open_url(lua_State *L) return 1; } +/******************************************************************************/ +int ModApiMainMenu::l_open_url_dialog(lua_State *L) +{ + GUIEngine* engine = getGuiEngine(L); + sanity_check(engine != NULL); + + std::string url = luaL_checkstring(L, 1); + + GUIOpenURLMenu* openURLMenu = + new GUIOpenURLMenu(engine->m_rendering_engine->get_gui_env(), + engine->m_parent, -1, engine->m_menumanager, + engine->m_texture_source.get(), url); + openURLMenu->drop(); + return 1; +} + /******************************************************************************/ int ModApiMainMenu::l_open_dir(lua_State *L) { @@ -1136,7 +1160,9 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_active_irrlicht_device); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); + API_FCT(get_formspec_version); API_FCT(open_url); + API_FCT(open_url_dialog); API_FCT(open_dir); API_FCT(share_file); API_FCT(do_async_callback); @@ -1166,6 +1192,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(download_file); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); + API_FCT(get_formspec_version); API_FCT(get_language); API_FCT(gettext); } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 5535d2170..cb3e7f9ca 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -159,9 +159,13 @@ private: static int l_get_max_supp_proto(lua_State *L); + static int l_get_formspec_version(lua_State *L); + // other static int l_open_url(lua_State *L); + static int l_open_url_dialog(lua_State *L); + static int l_open_dir(lua_State *L); static int l_share_file(lua_State *L); From 1037ee2a55a4f94926c48aebba6dade7d8dcdb3b Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Mon, 1 Apr 2024 02:00:38 +0100 Subject: [PATCH 161/200] ContentDB redesign: Redesign package list dialog --- LICENSE.txt | 3 - builtin/mainmenu/content/contentdb.lua | 24 ++ builtin/mainmenu/content/dlg_contentdb.lua | 307 ++++++++++++------ builtin/mainmenu/content/dlg_package.lua | 7 +- textures/base/pack/button_hover_semitrans.png | Bin 0 -> 68 bytes textures/base/pack/button_press_semitrans.png | Bin 0 -> 68 bytes textures/base/pack/cdb_add.png | Bin 147 -> 0 bytes textures/base/pack/cdb_clear.png | Bin 150 -> 0 bytes textures/base/pack/cdb_viewonline.png | Bin 191 -> 0 bytes 9 files changed, 233 insertions(+), 108 deletions(-) create mode 100644 textures/base/pack/button_hover_semitrans.png create mode 100644 textures/base/pack/button_press_semitrans.png delete mode 100644 textures/base/pack/cdb_add.png delete mode 100644 textures/base/pack/cdb_clear.png delete mode 100644 textures/base/pack/cdb_viewonline.png diff --git a/LICENSE.txt b/LICENSE.txt index de76c7a80..03ca35100 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -57,12 +57,10 @@ srifqi: textures/base/pack/minimap_btn.png Zughy: - textures/base/pack/cdb_add.png textures/base/pack/cdb_downloading.png textures/base/pack/cdb_queued.png textures/base/pack/cdb_update.png textures/base/pack/cdb_update_cropped.png - textures/base/pack/cdb_viewonline.png textures/base/pack/settings_btn.png textures/base/pack/settings_info.png textures/base/pack/settings_reset.png @@ -79,7 +77,6 @@ kilbith: textures/base/pack/progress_bar_bg.png SmallJoker: - textures/base/pack/cdb_clear.png textures/base/pack/server_favorite_delete.png (based on server_favorite.png) DS: diff --git a/builtin/mainmenu/content/contentdb.lua b/builtin/mainmenu/content/contentdb.lua index 4d59826dd..5d6d6c482 100644 --- a/builtin/mainmenu/content/contentdb.lua +++ b/builtin/mainmenu/content/contentdb.lua @@ -641,3 +641,27 @@ function contentdb.get_full_package_info(package, callback) callback(nil) end end + + +function contentdb.get_formspec_padding() + -- Padding is increased on Android to account for notches + -- TODO: use Android API to determine size of cut outs + return { x = PLATFORM == "Android" and 1 or 0.5, y = PLATFORM == "Android" and 0.25 or 0.5 } +end + + +function contentdb.get_formspec_size() + local window = core.get_window_info() + local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y } + + -- Minimum formspec size + local min_x = 15.5 + local min_y = 10 + if size.x < min_x or size.y < min_y then + local scale = math.max(min_x / size.x, min_y / size.y) + size.x = size.x * scale + size.y = size.y * scale + end + + return size +end diff --git a/builtin/mainmenu/content/dlg_contentdb.lua b/builtin/mainmenu/content/dlg_contentdb.lua index 025430bfa..a86815b77 100644 --- a/builtin/mainmenu/content/dlg_contentdb.lua +++ b/builtin/mainmenu/content/dlg_contentdb.lua @@ -26,23 +26,17 @@ end -- Filter local search_string = "" local cur_page = 1 -local num_per_page = 5 -local filter_type = 1 -local filter_types_titles = { - fgettext("All packages"), - fgettext("Games"), - fgettext("Mods"), - fgettext("Texture packs"), -} +local filter_type -- Automatic package installation local auto_install_spec = nil -local filter_types_type = { - nil, - "game", - "mod", - "txp", + +local filter_type_names = { + { "type_all", nil }, + { "type_game", "game" }, + { "type_mod", "mod" }, + { "type_txp", "txp" }, } @@ -103,7 +97,7 @@ end local function sort_and_filter_pkgs() contentdb.update_paths() contentdb.sort_packages() - contentdb.filter_packages(search_string, filter_types_type[filter_type]) + contentdb.filter_packages(search_string, filter_type) local auto_install_pkg = resolve_auto_install_spec() if auto_install_pkg then @@ -134,72 +128,151 @@ local function load() end -local function get_info_formspec(text) - local H = 9.5 +local function get_info_formspec(size, padding, text) return table.concat({ "formspec_version[6]", - "size[15.75,9.5]", - core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "position[0.5,0.55]", + "size[", size.x, ",", size.y, "]", + "padding[0,0]", + "bgcolor[;true]", - "label[4,4.35;", text, "]", - "container[0,", H - 0.8 - 0.375, "]", - "button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]", + "label[", padding.x + 3.625, ",4.35;", text, "]", + "container[", padding.x, ",", size.y - 0.8 - padding.y, "]", + "button[0,0;2,0.8;back;", fgettext("Back"), "]", "container_end[]", }) end +-- Determines how to fit `num_per_page` into `size` space +local function fit_cells(num_per_page, size) + local cell_spacing = 0.5 + local columns = 1 + local cell_w, cell_h + -- Fit cells into the available height + while true do + cell_w = (size.x - (columns-1)*cell_spacing) / columns + cell_h = cell_w / 4 + + local required_height = math.ceil(num_per_page / columns) * (cell_h + cell_spacing) - cell_spacing + -- Add 0.1 to be more lenient + if required_height <= size.y + 0.1 then + break + end + + columns = columns + 1 + end + + return cell_spacing, columns, cell_w, cell_h +end + + +local function calculate_num_per_page() + local size = contentdb.get_formspec_size() + local padding = contentdb.get_formspec_padding() + local window = core.get_window_info() + + size.x = size.x - padding.x * 2 + size.y = size.y - padding.y * 2 - 1.425 - 0.25 - 0.8 + + local coordToPx = window.size.x / window.max_formspec_size.x / window.real_gui_scaling + + local num_per_page = 12 + while num_per_page > 2 do + local _, _, cell_w, _ = fit_cells(num_per_page, size) + if cell_w * coordToPx > 350 then + break + end + + num_per_page = num_per_page - 1 + end + return num_per_page +end + + local function get_formspec(dlgdata) + local window_padding = contentdb.get_formspec_padding() + local size = contentdb.get_formspec_size() + if contentdb.loading then - return get_info_formspec(fgettext("Loading...")) + return get_info_formspec(size, window_padding, fgettext("Loading...")) end if contentdb.load_error then - return get_info_formspec(fgettext("No packages could be retrieved")) + return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved")) end assert(contentdb.load_ok) contentdb.update_paths() + local num_per_page = dlgdata.num_per_page dlgdata.pagemax = math.max(math.ceil(#contentdb.packages / num_per_page), 1) if cur_page > dlgdata.pagemax then cur_page = 1 end - local W = 15.75 - local H = 9.5 + local W = size.x - window_padding.x * 2 + local H = size.y - window_padding.y * 2 + + local category_x = 0 + local number_category_buttons = 4 + local max_button_w = (W - 0.375 - 0.25 - 7) / number_category_buttons + local category_button_w = math.min(max_button_w, 3) + local function make_category_button(name, label, selected) + category_x = category_x + 1 + local color = selected and mt_color_green or "" + return ("style[%s;bgcolor=%s]button[%f,0;%f,0.8;%s;%s]"):format(name, color, + (category_x - 1) * category_button_w, category_button_w, name, label) + end + + + local selected_type = filter_type + + local search_box_width = W - 0.375 - 0.25 - 2*0.8 + - number_category_buttons * category_button_w local formspec = { - "formspec_version[6]", - "size[15.75,9.5]", - core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "position[0.5,0.55]", + "formspec_version[7]", + "size[", size.x, ",", size.y, "]", + "padding[0,0]", + "bgcolor[;true]", - "style[status,downloading,queued;border=false]", + "container[", window_padding.x, ",", window_padding.y, "]", - "container[0.375,0.375]", - "field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]", + -- Top-left: categories + make_category_button("type_all", fgettext("All"), selected_type == nil), + make_category_button("type_game", fgettext("Games"), selected_type == "game"), + make_category_button("type_mod", fgettext("Mods"), selected_type == "mod"), + make_category_button("type_txp", fgettext("Texture Packs"), selected_type == "txp"), + + -- Top-right: Search + "container[", W - search_box_width - 0.8*2, ",0]", + "field[0,0;", search_box_width, ",0.8;search_string;;", core.formspec_escape(search_string), "]", "field_enter_after_edit[search_string;true]", - "image_button[7.3,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]", - "image_button[8.125,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]", - "dropdown[9.175,0;2.7875,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]", + "image_button[", search_box_width, ",0;0.8,0.8;", + core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]", + "image_button[", search_box_width + 0.8, ",0;0.8,0.8;", + core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]", "container_end[]", - -- Page nav buttons - "container[0,", H - 0.8 - 0.375, "]", - "button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]", + -- Bottom strip start + "container[0,", H - 0.8, "]", + "button[0,0;2,0.8;back;", fgettext("Back"), "]", - "container[", W - 0.375 - 0.8*4 - 2, ",0]", - "image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]", - "image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]", + -- Bottom-center: Page nav buttons + "container[", (W - 1*4 - 2) / 2, ",0]", + "image_button[0,0;1,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]", + "image_button[1,0;1,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]", "style[pagenum;border=false]", - "button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]", - "image_button[3.6,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]", - "image_button[4.4,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]", - "container_end[]", + "button[2,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]", + "image_button[4,0;1,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]", + "image_button[5,0;1,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]", + "container_end[]", -- page nav end - "container_end[]", + -- Bottom-right: updating + "container[", W - 3, ",0]", + "style[status,downloading,queued;border=false]", } if contentdb.number_downloading > 0 then - formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;downloading;" + formspec[#formspec + 1] = "button[0,0;3,0.8;downloading;" if #contentdb.download_queue > 0 then formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued", contentdb.number_downloading, #contentdb.download_queue) @@ -218,16 +291,19 @@ local function get_formspec(dlgdata) end if num_avail_updates == 0 then - formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;status;" + formspec[#formspec + 1] = "button[0,0;3,0.8;status;" formspec[#formspec + 1] = fgettext("No updates") formspec[#formspec + 1] = "]" else - formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;update_all;" + formspec[#formspec + 1] = "button[0,0;3,0.8;update_all;" formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates) formspec[#formspec + 1] = "]" end end + formspec[#formspec + 1] = "container_end[]" -- updating end + formspec[#formspec + 1] = "container_end[]" -- bottom strip end + if #contentdb.packages == 0 then formspec[#formspec + 1] = "label[4,4.75;" formspec[#formspec + 1] = fgettext("No results") @@ -239,46 +315,85 @@ local function get_formspec(dlgdata) formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors formspec[#formspec + 1] = "tooltip[queued;" .. fgettext("Queued") .. tooltip_colors + formspec[#formspec + 1] = "container[0,1.425]" + + local cell_spacing, columns, cell_w, cell_h = fit_cells(num_per_page, { + x = W, + y = H - 1.425 - 0.25 - 0.8 + }) + local img_w = cell_h * 3 / 2 + local start_idx = (cur_page - 1) * num_per_page + 1 for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do local package = contentdb.packages[i] - local container_y = (i - start_idx) * 1.375 + (2*0.375 + 0.8) - formspec[#formspec + 1] = "container[0.375," - formspec[#formspec + 1] = container_y - formspec[#formspec + 1] = "]" - -- image - formspec[#formspec + 1] = "image[0,0;1.5,1;" - formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package, package.thumbnail, 1)) - formspec[#formspec + 1] = "]" + table.insert_all(formspec, { + "container[", + (cell_w + cell_spacing) * ((i - start_idx) % columns), + ",", + (cell_h + cell_spacing) * math.floor((i - start_idx) / columns), + "]", - -- title - formspec[#formspec + 1] = "label[1.875,0.1;" - formspec[#formspec + 1] = core.formspec_escape( - core.colorize(mt_color_green, package.title) .. - core.colorize("#BFBFBF", " by " .. package.author)) - formspec[#formspec + 1] = "]" + "box[0,0;", cell_w, ",", cell_h, ";#ffffff11]", - -- button - formspec[#formspec + 1] = "button[" - formspec[#formspec + 1] = W-0.375*2-2 - formspec[#formspec + 1] = ",0.1;2,0.7;view_" - formspec[#formspec + 1] = i - formspec[#formspec + 1] = ";" - formspec[#formspec + 1] = fgettext("View") - formspec[#formspec + 1] = "]" + -- image, + "image[0,0;", img_w, ",", cell_h, ";", + core.formspec_escape(get_screenshot(package, package.thumbnail, 2)), "]", - -- description - local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15 - formspec[#formspec + 1] = "textarea[1.855,0.3;" - formspec[#formspec + 1] = tostring(description_width) - formspec[#formspec + 1] = ",0.8;;;" - formspec[#formspec + 1] = core.formspec_escape(package.short_description) - formspec[#formspec + 1] = "]" + "label[", img_w + 0.25 + 0.05, ",0.5;", + core.formspec_escape( + core.colorize(mt_color_green, package.title) .. + core.colorize("#BFBFBF", " by " .. package.author)), "]", - formspec[#formspec + 1] = "container_end[]" + "textarea[", img_w + 0.25, ",0.75;", cell_w - img_w - 0.25, ",", cell_h - 0.75, ";;;", + core.formspec_escape(package.short_description), "]", + + "style[view_", i, ";border=false]", + "style[view_", i, ":hovered;bgimg=", core.formspec_escape(defaulttexturedir .. "button_hover_semitrans.png"), "]", + "style[view_", i, ":pressed;bgimg=", core.formspec_escape(defaulttexturedir .. "button_press_semitrans.png"), "]", + "button[0,0;", cell_w, ",", cell_h, ";view_", i, ";]", + }) + + if package.featured then + table.insert_all(formspec, { + "tooltip[0,0;0.8,0.8;", fgettext("Featured"), "]", + "image[0.2,0.2;0.4,0.4;", defaulttexturedir, "server_favorite.png]", + }) + end + + table.insert_all(formspec, { + "container[", cell_w - 0.625,",", 0.25, "]", + }) + + if package.downloading then + table.insert_all(formspec, { + "animated_image[0,0;0.5,0.5;downloading;", defaulttexturedir, "cdb_downloading.png;3;400;;]", + }) + elseif package.queued then + table.insert_all(formspec, { + "image[0,0;0.5,0.5;", defaulttexturedir, "cdb_queued.png]", + }) + elseif package.path then + if package.installed_release < package.release then + table.insert_all(formspec, { + "image[0,0;0.5,0.5;", defaulttexturedir, "cdb_update.png]", + }) + else + table.insert_all(formspec, { + "image[0.1,0.1;0.3,0.3;", defaulttexturedir, "checkbox_64.png]", + }) + end + end + + table.insert_all(formspec, { + "container_end[]", + "container_end[]", + }) end + formspec[#formspec + 1] = "container_end[]" + formspec[#formspec + 1] = "container_end[]" + return table.concat(formspec) end @@ -287,14 +402,14 @@ local function handle_submit(this, fields) if fields.search or fields.key_enter_field == "search_string" then search_string = fields.search_string:trim() cur_page = 1 - contentdb.filter_packages(search_string, filter_types_type[filter_type]) + contentdb.filter_packages(search_string, filter_type) return true end if fields.clear then search_string = "" cur_page = 1 - contentdb.filter_packages("", filter_types_type[filter_type]) + contentdb.filter_packages("", filter_type) return true end @@ -330,12 +445,11 @@ local function handle_submit(this, fields) return true end - if fields.type then - local new_type = table.indexof(filter_types_titles, fields.type) - if new_type ~= filter_type then - filter_type = new_type + for _, pair in ipairs(filter_type_names) do + if fields[pair[1]] then + filter_type = pair[2] cur_page = 1 - contentdb.filter_packages(search_string, filter_types_type[filter_type]) + contentdb.filter_packages(search_string, filter_type) return true end end @@ -351,13 +465,14 @@ local function handle_submit(this, fields) return true end + local num_per_page = this.data.num_per_page local start_idx = (cur_page - 1) * num_per_page + 1 assert(start_idx ~= nil) for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do local package = contentdb.packages[i] assert(package) - if fields["view_" .. i] then + if fields["view_" .. i] or fields["title_" .. i] or fields["author_" .. i] then local dlg = create_package_dialog(package) dlg:set_parent(this) this:hide() @@ -372,8 +487,8 @@ end local function handle_events(event) if event == "DialogShow" then - -- On touchscreen, don't show the "MINETEST" header behind the dialog. - mm_game_theme.set_engine(core.settings:get_bool("touch_gui")) + -- Don't show the "MINETEST" header behind the dialog. + mm_game_theme.set_engine(true) -- If ContentDB is already loaded, auto-install packages here. do_auto_install() @@ -395,17 +510,7 @@ end function create_contentdb_dlg(type, install_spec) search_string = "" cur_page = 1 - if type then - -- table.indexof does not work on tables that contain `nil` - for i, v in pairs(filter_types_type) do - if v == type then - filter_type = i - break - end - end - else - filter_type = 1 - end + filter_type = type -- Keep the old auto_install_spec if the caller doesn't specify one. if install_spec then @@ -414,8 +519,10 @@ function create_contentdb_dlg(type, install_spec) load() - return dialog_create("contentdb", + local dlg = dialog_create("contentdb", get_formspec, handle_submit, handle_events) + dlg.data.num_per_page = calculate_num_per_page() + return dlg end diff --git a/builtin/mainmenu/content/dlg_package.lua b/builtin/mainmenu/content/dlg_package.lua index 78bdf2e71..5b9db4860 100644 --- a/builtin/mainmenu/content/dlg_package.lua +++ b/builtin/mainmenu/content/dlg_package.lua @@ -32,11 +32,8 @@ end local function get_formspec(data) - -- Padding is increased on Android to account for notches - -- TODO: use Android API to determine size of cut outs - local window_padding = { x = PLATFORM == "Android" and 1 or 0.5, y = PLATFORM == "Android" and 0.25 or 0.5 } - local window = core.get_window_info() - local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y } + local window_padding = contentdb.get_formspec_padding() + local size = contentdb.get_formspec_size() size.x = math.min(size.x, 20) local W = size.x - window_padding.x * 2 local H = size.y - window_padding.y * 2 diff --git a/textures/base/pack/button_hover_semitrans.png b/textures/base/pack/button_hover_semitrans.png new file mode 100644 index 0000000000000000000000000000000000000000..5cf294eadeac04843d2624858272c1024d73edc9 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-5DwYoALscQm;@O4cLvqQ Q0)-hoUHx3vIVCg!09m^XPXGV_ literal 0 HcmV?d00001 diff --git a/textures/base/pack/button_press_semitrans.png b/textures/base/pack/button_press_semitrans.png new file mode 100644 index 0000000000000000000000000000000000000000..ba0ddd5107b8029092a4ca60b331cf24a0fe15aa GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-5DwYoga_;l3_=V7Yxyk$ PfWi!(u6{1-oD!MEv-?~+u3<>fMmhS q|M8FiKQ>~|V_o+4kG#frVTP$m0*-x0uSx?AVDNPHb6Mw<&;$S-YA!PX diff --git a/textures/base/pack/cdb_clear.png b/textures/base/pack/cdb_clear.png deleted file mode 100644 index d5df4a067f5e207257eecef0fffd4f98cdf88984..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^3Lq@N0wg;h=h*-_(J z_(MZPU%!2gOoPmsCrMqh8^7H8|NlwSuOr7dus=DX8oK%UrR*7Pf`^w!NlBC*Si$Tj xYoH)>u3+61;WK$w!4@ewplLUl7&N|CO}ul-Vm$ z-@z#j6k;q1@(X5gcy=QV$cgiGaSW-rm7E|E(%|Xo$v9PiNr%#+6&g7jDlJPKJOXY6 z1Ozf}ZgpxuS3das?*KDHmN@U5yct51fi^LCy85}Sb4q9e08d;#XaE2J From 291c3ad0c1bc4ae095364cfd109ceb4f2c9dc7b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Tue, 8 Oct 2024 21:44:44 +0200 Subject: [PATCH 162/200] Document performance cost of use_texture_alpha=blend (#15244) --- doc/lua_api.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 2fb2950d3..9cc25172e 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -9554,12 +9554,18 @@ Used by `minetest.register_node`. use_texture_alpha = ..., -- Specifies how the texture's alpha channel will be used for rendering. - -- possible values: - -- * "opaque": Node is rendered opaque regardless of alpha channel - -- * "clip": A given pixel is either fully see-through or opaque - -- depending on the alpha channel being below/above 50% in value - -- * "blend": The alpha channel specifies how transparent a given pixel - -- of the rendered node is + -- Possible values: + -- * "opaque": + -- Node is rendered opaque regardless of alpha channel. + -- * "clip": + -- A given pixel is either fully see-through or opaque + -- depending on the alpha channel being below/above 50% in value. + -- Use this for nodes with fully transparent and fully opaque areas. + -- * "blend": + -- The alpha channel specifies how transparent a given pixel + -- of the rendered node is. This comes at a performance cost. + -- Only use this when correct rendering + -- among semitransparent nodes is necessary. -- The default is "opaque" for drawtypes normal, liquid and flowingliquid, -- mesh and nodebox or "clip" otherwise. -- If set to a boolean value (deprecated): true either sets it to blend From 13f533d4902b3e18a1ab73540555ae979bf139d7 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 8 Oct 2024 21:45:27 +0200 Subject: [PATCH 163/200] scrollcontainer: Add automatic scrollbar calculation (#14623) New parameter 'content padding'. When specified, the scrollbar max value is calculated automatically. This aims to reduce manual calculation functions. --- builtin/mainmenu/settings/dlg_settings.lua | 21 ++-------- doc/lua_api.md | 10 ++++- games/devtest/mods/testformspec/formspec.lua | 15 ++++++- src/gui/guiFormSpecMenu.cpp | 9 +++- src/gui/guiScrollBar.h | 1 + src/gui/guiScrollContainer.cpp | 44 ++++++++++++++++++++ src/gui/guiScrollContainer.h | 14 ++++--- src/network/networkprotocol.cpp | 2 +- 8 files changed, 87 insertions(+), 29 deletions(-) diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 4842b2e1a..3da80e877 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -443,19 +443,6 @@ local function build_page_components(page) end ---- Creates a scrollbaroptions for a scroll_container --- --- @param visible_l the length of the scroll_container and scrollbar --- @param total_l length of the scrollable area --- @param scroll_factor as passed to scroll_container -local function make_scrollbaroptions_for_scroll_container(visible_l, total_l, scroll_factor) - assert(total_l >= visible_l) - local max = total_l - visible_l - local thumb_size = (visible_l / total_l) * max - return ("scrollbaroptions[min=0;max=%f;thumbsize=%f]"):format(max / scroll_factor, thumb_size / scroll_factor) -end - - local formspec_show_hack = false @@ -517,8 +504,8 @@ local function get_formspec(dialogdata) "tooltip[search;", fgettext("Search"), "]", "tooltip[search_clear;", fgettext("Clear"), "]", "container_end[]", - "scroll_container[0.25,1.25;", tostring(left_pane_width), ",", - tostring(tabsize.height - 1.5), ";leftscroll;vertical;0.1]", + ("scroll_container[0.25,1.25;%f,%f;leftscroll;vertical;0.1;0]"):format( + left_pane_width, tabsize.height - 1.5), "style_type[button;border=false;bgcolor=#3333]", "style_type[button:hover;border=false;bgcolor=#6663]", } @@ -548,7 +535,6 @@ local function get_formspec(dialogdata) fs[#fs + 1] = "scroll_container_end[]" if y >= tabsize.height - 1.25 then - fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height - 1.5, y, 0.1) fs[#fs + 1] = ("scrollbar[%f,1.25;%f,%f;vertical;leftscroll;%f]"):format( left_pane_width + 0.25, scrollbar_w, tabsize.height - 1.5, dialogdata.leftscroll or 0) end @@ -560,7 +546,7 @@ local function get_formspec(dialogdata) end local right_pane_width = tabsize.width - left_pane_width - 0.375 - 2*scrollbar_w - 0.25 - fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1]"):format( + fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1;0.25]"):format( tabsize.width - right_pane_width - scrollbar_w, right_pane_width, tabsize.height) y = 0.25 @@ -616,7 +602,6 @@ local function get_formspec(dialogdata) fs[#fs + 1] = "scroll_container_end[]" if y >= tabsize.height then - fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height, y + 0.375, 0.1) fs[#fs + 1] = ("scrollbar[%f,0;%f,%f;vertical;rightscroll;%f]"):format( tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0) end diff --git a/doc/lua_api.md b/doc/lua_api.md index 9cc25172e..92c19545f 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -2747,6 +2747,8 @@ Version History * Formspec version 7 (5.8.0): * style[]: Add focused state for buttons * Add field_enter_after_edit[] (experimental) +* Formspec version 8 (5.10.0) + * scroll_container[]: content padding parameter Elements -------- @@ -2830,7 +2832,7 @@ Elements * End of a container, following elements are no longer relative to this container. -### `scroll_container[,;,;;;]` +### `scroll_container[,;,;;;;]` * Start of a scroll_container block. All contained elements will ... * take the scroll_container coordinate as position origin, @@ -2839,6 +2841,12 @@ Elements * be clipped to the rectangle defined by `X`, `Y`, `W` and `H`. * `orientation`: possible values are `vertical` and `horizontal`. * `scroll factor`: optional, defaults to `0.1`. +* `content padding`: (optional), in formspec coordinate units + * If specified, the scrollbar properties `max` and `thumbsize` are calculated automatically + based on the content size plus `content padding` at the end of the container. `min` is set to 0. + * Negative `scroll factor` is not supported. + * When active, `scrollbaroptions[]` has no effect on the affected properties. + * Defaults to empty value (= disabled). * Nesting is possible. * Some elements might work a little different if they are in a scroll_container. * Note: If you want the scroll_container to actually work, you also need to add a diff --git a/games/devtest/mods/testformspec/formspec.lua b/games/devtest/mods/testformspec/formspec.lua index 8d0b759f5..f8f17798b 100644 --- a/games/devtest/mods/testformspec/formspec.lua +++ b/games/devtest/mods/testformspec/formspec.lua @@ -299,7 +299,18 @@ local scroll_fs = "scrollbaroptions[max=170]".. -- lowest seen pos is: 0.1*170+6=23 (factor*max+height) "scrollbar[7.5,0;0.3,4;vertical;scrbar;0]".. "scrollbar[8,0;0.3,4;vertical;scrbarhmmm;0]".. - "dropdown[0,6;2;hmdrpdwnnn;Outside,of,container;1]" + "dropdown[0,6;2;hmdrpdwnnn;Outside,of,container;1]".. + "scroll_container[0,8;10,4;scrbar420;vertical;0.1;2]".. + "button[0.5,0.5;10,1;;Container with padding=2]".. + "list[current_player;main;0,5;8,4;]".. + "scroll_container_end[]".. + "scrollbar[10.1,8;0.5,4;vertical;scrbar420;0]".. + -- Buttons for scale comparison + "button[11,8;1,1;;0]".. + "button[11,9;1,1;;1]".. + "button[11,10;1,1;;2]".. + "button[11,11;1,1;;3]".. + "button[11,12;1,1;;4]" --style_type[label;textcolor=green] --label[0,0;Green] @@ -462,7 +473,7 @@ mouse control = true] ]], -- Scroll containers - "formspec_version[3]size[12,13]" .. + "formspec_version[7]size[12,13]" .. scroll_fs, -- Sound diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 40a445a0c..c9366f83f 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -356,7 +356,7 @@ void GUIFormSpecMenu::parseContainerEnd(parserData* data, const std::string &) void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &element) { std::vector parts; - if (!precheckElement("scroll_container start", element, 4, 5, parts)) + if (!precheckElement("scroll_container start", element, 4, 6, parts)) return; std::vector v_pos = split(parts[0], ','); @@ -367,6 +367,12 @@ void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string & if (parts.size() >= 5 && !parts[4].empty()) scroll_factor = stof(parts[4]); + std::optional content_padding_px; + if (parts.size() >= 6 && !parts[5].empty()) { + std::vector v_size = { parts[5], parts[5] }; + content_padding_px = getRealCoordinateGeometry(v_size)[orientation == "vertical" ? 1 : 0]; + } + MY_CHECKPOS("scroll_container", 0); MY_CHECKGEOM("scroll_container", 1); @@ -405,6 +411,7 @@ void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string & GUIScrollContainer *mover = new GUIScrollContainer(Environment, clipper, spec_mover.fid, rect_mover, orientation, scroll_factor); + mover->setContentPadding(content_padding_px); data->current_parent = mover; diff --git a/src/gui/guiScrollBar.h b/src/gui/guiScrollBar.h index 05e195aed..af3bc4652 100644 --- a/src/gui/guiScrollBar.h +++ b/src/gui/guiScrollBar.h @@ -45,6 +45,7 @@ public: s32 getSmallStep() const { return small_step; } s32 getPos() const; s32 getTargetPos() const; + bool isHorizontal() const { return is_horizontal; } void setMax(const s32 &max); void setMin(const s32 &min); diff --git a/src/gui/guiScrollContainer.cpp b/src/gui/guiScrollContainer.cpp index 2d71f3453..13ba5c35f 100644 --- a/src/gui/guiScrollContainer.cpp +++ b/src/gui/guiScrollContainer.cpp @@ -67,6 +67,50 @@ void GUIScrollContainer::draw() } } +void GUIScrollContainer::setScrollBar(GUIScrollBar *scrollbar) +{ + m_scrollbar = scrollbar; + + if (m_scrollbar && m_content_padding_px.has_value() && m_scrollfactor != 0.0f) { + // Set the scrollbar max value based on the content size. + + // Get content size based on elements + core::rect size; + for (gui::IGUIElement *e : Children) { + core::rect abs_rect = e->getAbsolutePosition(); + size.addInternalPoint(abs_rect.LowerRightCorner); + } + + s32 visible_content_px = ( + m_orientation == VERTICAL + ? AbsoluteClippingRect.getHeight() + : AbsoluteClippingRect.getWidth() + ); + + s32 total_content_px = *m_content_padding_px + ( + m_orientation == VERTICAL + ? (size.LowerRightCorner.Y - AbsoluteClippingRect.UpperLeftCorner.Y) + : (size.LowerRightCorner.X - AbsoluteClippingRect.UpperLeftCorner.X) + ); + + s32 hidden_content_px = std::max(0, total_content_px - visible_content_px); + m_scrollbar->setMin(0); + m_scrollbar->setMax(std::ceil(hidden_content_px / std::fabs(m_scrollfactor))); + + // Note: generally, the scrollbar has the same size as the scroll container. + // However, in case it isn't, proportional adjustments are needed. + s32 scrollbar_px = ( + m_scrollbar->isHorizontal() + ? m_scrollbar->getRelativePosition().getWidth() + : m_scrollbar->getRelativePosition().getHeight() + ); + + m_scrollbar->setPageSize((total_content_px * scrollbar_px) / visible_content_px); + } + + updateScrolling(); +} + void GUIScrollContainer::updateScrolling() { s32 pos = m_scrollbar->getPos(); diff --git a/src/gui/guiScrollContainer.h b/src/gui/guiScrollContainer.h index 9e3ec6e93..d6871a53e 100644 --- a/src/gui/guiScrollContainer.h +++ b/src/gui/guiScrollContainer.h @@ -34,17 +34,18 @@ public: virtual void draw() override; + inline void setContentPadding(std::optional padding) + { + m_content_padding_px = padding; + } + inline void onScrollEvent(gui::IGUIElement *caller) { if (caller == m_scrollbar) updateScrolling(); } - inline void setScrollBar(GUIScrollBar *scrollbar) - { - m_scrollbar = scrollbar; - updateScrolling(); - } + void setScrollBar(GUIScrollBar *scrollbar); private: enum OrientationEnum @@ -56,7 +57,8 @@ private: GUIScrollBar *m_scrollbar; OrientationEnum m_orientation; - f32 m_scrollfactor; + f32 m_scrollfactor; //< scrollbar pos * scrollfactor = scroll offset in pixels + std::optional m_content_padding_px; //< in pixels void updateScrolling(); }; diff --git a/src/network/networkprotocol.cpp b/src/network/networkprotocol.cpp index 350b4d734..38b958d24 100644 --- a/src/network/networkprotocol.cpp +++ b/src/network/networkprotocol.cpp @@ -63,4 +63,4 @@ const u16 LATEST_PROTOCOL_VERSION = 46; // See also formspec [Version History] in doc/lua_api.md -const u16 FORMSPEC_API_VERSION = 7; +const u16 FORMSPEC_API_VERSION = 8; From 6ac4447134506b766de44b69a903b77e07853936 Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 9 Oct 2024 15:08:03 +0200 Subject: [PATCH 164/200] Make bloom parameters server-controlled (#15231) --- builtin/mainmenu/settings/dlg_settings.lua | 5 +++ builtin/settingtypes.txt | 22 ----------- doc/lua_api.md | 28 ++++++++++++-- games/devtest/mods/lighting/init.lua | 44 +++++++++++++++------- src/client/game.cpp | 31 +++++---------- src/client/renderingengine.cpp | 1 - src/client/renderingengine.h | 1 - src/defaultsettings.cpp | 3 -- src/lighting.h | 3 ++ src/network/clientpackethandler.cpp | 5 +++ src/script/lua_api/l_object.cpp | 16 ++++++++ src/server.cpp | 2 + 12 files changed, 95 insertions(+), 66 deletions(-) diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 3da80e877..182319be0 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -161,6 +161,11 @@ local function load() note.requires = get_setting_info("enable_auto_exposure").requires table.insert(content, idx, note) + idx = table.indexof(content, "enable_bloom") + 1 + note = component_funcs.note(fgettext_ne("(The game will need to enable bloom as well)")) + note.requires = get_setting_info("enable_bloom").requires + table.insert(content, idx, note) + idx = table.indexof(content, "enable_volumetric_lighting") + 1 note = component_funcs.note(fgettext_ne("(The game will need to enable volumetric lighting as well)")) note.requires = get_setting_info("enable_volumetric_lighting").requires diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 342fc24a6..2f7af3fae 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -616,34 +616,12 @@ exposure_compensation (Exposure compensation) float 0.0 -1.0 1.0 # Requires: shaders, enable_post_processing debanding (Enable Debanding) bool true -[**Bloom] - # Set to true to enable bloom effect. # Bright colors will bleed over the neighboring objects. # # Requires: shaders, enable_post_processing enable_bloom (Enable Bloom) bool false -# Defines how much bloom is applied to the rendered image -# Smaller values make bloom more subtle -# Range: from 0.01 to 1.0, default: 0.05 -# -# Requires: shaders, enable_post_processing, enable_bloom -bloom_intensity (Bloom Intensity) float 0.05 0.01 1.0 - -# Defines the magnitude of bloom overexposure. -# Range: from 0.1 to 10.0, default: 1.0 -# -# Requires: shaders, enable_post_processing, enable_bloom -bloom_strength_factor (Bloom Strength Factor) float 1.0 0.1 10.0 - -# Logical value that controls how far the bloom effect spreads -# from the bright objects. -# Range: from 0.1 to 8, default: 1 -# -# Requires: shaders, enable_post_processing, enable_bloom -bloom_radius (Bloom Radius) float 1 0.1 8 - # Set to true to enable volumetric lighting effect (a.k.a. "Godrays"). # # Requires: shaders, enable_post_processing, enable_bloom diff --git a/doc/lua_api.md b/doc/lua_api.md index 92c19545f..ed41d7a22 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8616,23 +8616,43 @@ child will follow movement and rotation of that bone. * values < 0 cause an effect similar to inversion, but keeping original luma and being symmetrical in terms of saturation (eg. -1 and 1 is the same saturation and luma, but different hues) + * This value has no effect on clients who have shaders or post-processing disabled. * `shadows` is a table that controls ambient shadows + * This has no effect on clients who have the "Dynamic Shadows" effect disabled. * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness) - * This value has no effect on clients who have the "Dynamic Shadows" shader disabled. * `tint` tints the shadows with the provided color, with RGB values ranging from 0 to 255. (default `{r=0, g=0, b=0}`) - * This value has no effect on clients who have the "Dynamic Shadows" shader disabled. * `exposure` is a table that controls automatic exposure. The basic exposure factor equation is `e = 2^exposure_correction / clamp(luminance, 2^luminance_min, 2^luminance_max)` + * This has no effect on clients who have the "Automatic Exposure" effect disabled. * `luminance_min` set the lower luminance boundary to use in the calculation (default: `-3.0`) * `luminance_max` set the upper luminance boundary to use in the calculation (default: `-3.0`) * `exposure_correction` correct observed exposure by the given EV value (default: `0.0`) * `speed_dark_bright` set the speed of adapting to bright light (default: `1000.0`) * `speed_bright_dark` set the speed of adapting to dark scene (default: `1000.0`) * `center_weight_power` set the power factor for center-weighted luminance measurement (default: `1.0`) + * `bloom` is a table that controls bloom. + * This has no effect on clients with protocol version < 46 or clients who + have the "Bloom" effect disabled. + * `intensity` defines much bloom is applied to the rendered image. + * Recommended range: from 0.0 to 1.0, default: 0.05 + * If set to 0, bloom is disabled. + * The default value is to be changed from 0.05 to 0 in the future. + If you wish to keep the current default value, you should set it + explicitly. + * `strength_factor` defines the magnitude of bloom overexposure. + * Recommended range: from 0.1 to 10.0, default: 1.0 + * `radius` is a logical value that controls how far the bloom effect + spreads from the bright objects. + * Recommended range: from 0.1 to 8.0, default: 1.0 + * The behavior of values outside the recommended range is unspecified. * `volumetric_light`: is a table that controls volumetric light (a.k.a. "godrays") - * `strength`: sets the strength of the volumetric light effect from 0 (off, default) to 1 (strongest) - * This value has no effect on clients who have the "Volumetric Lighting" or "Bloom" shaders disabled. + * This has no effect on clients who have the "Volumetric Lighting" or "Bloom" effects disabled. + * `strength`: sets the strength of the volumetric light effect from 0 (off, default) to 1 (strongest). + * `0.2` is a reasonable standard value. + * Currently, bloom `intensity` and `strength_factor` affect volumetric + lighting `strength` and vice versa. This behavior is to be changed + in the future, do not rely on it. * `get_lighting()`: returns the current state of lighting for the player. * Result is a table with the same fields as `light_definition` in `set_lighting`. diff --git a/games/devtest/mods/lighting/init.lua b/games/devtest/mods/lighting/init.lua index 7b4392fb8..20448d925 100644 --- a/games/devtest/mods/lighting/init.lua +++ b/games/devtest/mods/lighting/init.lua @@ -14,7 +14,21 @@ local lighting_sections = { {n = "speed_bright_dark", d = "Dark scene adaptation speed", min = -10, max = 10, type="log2"}, {n = "center_weight_power", d = "Power factor for center-weighting", min = 0.1, max = 10}, } - } + }, + { + n = "bloom", d = "Bloom", + entries = { + {n = "intensity", d = "Intensity", min = 0, max = 1}, + {n = "strength_factor", d = "Strength Factor", min = 0.1, max = 10}, + {n = "radius", d = "Radius", min = 0.1, max = 8}, + }, + }, + { + n = "volumetric_light", d = "Volumetric Lighting", + entries = { + {n = "strength", d = "Strength", min = 0, max = 1}, + }, + }, } local function dump_lighting(lighting) @@ -59,38 +73,40 @@ minetest.register_chatcommand("set_lighting", { local lighting = player:get_lighting() local exposure = lighting.exposure or {} - local form = { - "formspec_version[2]", - "size[15,30]", - "position[0.99,0.15]", - "anchor[1,0]", - "padding[0.05,0.1]", - "no_prepend[]" - }; - + local content = {} local line = 1 for _,section in ipairs(lighting_sections) do local parameters = section.entries or {} local state = lighting[section.n] or {} - table.insert(form, "label[1,"..line..";"..section.d.."]") + table.insert(content, "label[1,"..line..";"..section.d.."]") line = line + 1 for _,v in ipairs(parameters) do - table.insert(form, "label[2,"..line..";"..v.d.."]") - table.insert(form, "scrollbaroptions[min=0;max=1000;smallstep=10;largestep=100;thumbsize=10]") + table.insert(content, "label[2,"..line..";"..v.d.."]") + table.insert(content, "scrollbaroptions[min=0;max=1000;smallstep=10;largestep=100;thumbsize=10]") local value = state[v.n] if v.type == "log2" then value = math.log(value or 1) / math.log(2) end local sb_scale = math.floor(1000 * (math.max(v.min, value or 0) - v.min) / (v.max - v.min)) - table.insert(form, "scrollbar[2,"..(line+0.7)..";12,1;horizontal;"..section.n.."."..v.n..";"..sb_scale.."]") + table.insert(content, "scrollbar[2,"..(line+0.7)..";12,1;horizontal;"..section.n.."."..v.n..";"..sb_scale.."]") line = line + 2.7 end line = line + 1 end + local form = { + "formspec_version[2]", + "size[15,", line, "]", + "position[0.99,0.15]", + "anchor[1,0]", + "padding[0.05,0.1]", + "no_prepend[]", + } + table.insert_all(form, content) + minetest.show_formspec(player_name, "lighting", table.concat(form)) local debug_value = dump_lighting(lighting) local debug_ui = player:hud_add({type="text", position={x=0.1, y=0.3}, scale={x=1,y=1}, alignment = {x=1, y=1}, text=debug_value, number=0xFFFFFF}) diff --git a/src/client/game.cpp b/src/client/game.cpp index 9dd3a0e83..f1e798a52 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -405,11 +405,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter float m_user_exposure_compensation; bool m_bloom_enabled; CachedPixelShaderSetting m_bloom_intensity_pixel{"bloomIntensity"}; - float m_bloom_intensity; CachedPixelShaderSetting m_bloom_strength_pixel{"bloomStrength"}; - float m_bloom_strength; CachedPixelShaderSetting m_bloom_radius_pixel{"bloomRadius"}; - float m_bloom_radius; CachedPixelShaderSetting m_saturation_pixel{"saturation"}; bool m_volumetric_light_enabled; CachedPixelShaderSetting @@ -421,11 +418,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_volumetric_light_strength_pixel{"volumetricLightStrength"}; - static constexpr std::array SETTING_CALLBACKS = { + static constexpr std::array SETTING_CALLBACKS = { "exposure_compensation", - "bloom_intensity", - "bloom_strength_factor", - "bloom_radius" }; public: @@ -433,12 +427,6 @@ public: { if (name == "exposure_compensation") m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f); - if (name == "bloom_intensity") - m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f); - if (name == "bloom_strength_factor") - m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f); - if (name == "bloom_radius") - m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f); } static void settingsCallback(const std::string &name, void *userdata) @@ -457,9 +445,6 @@ public: m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f); m_bloom_enabled = g_settings->getBool("enable_bloom"); - m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f); - m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f); - m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f); m_volumetric_light_enabled = g_settings->getBool("enable_volumetric_lighting") && m_bloom_enabled; } @@ -511,7 +496,9 @@ public: m_texel_size0_vertex.set(m_texel_size0, services); m_texel_size0_pixel.set(m_texel_size0, services); - const AutoExposure &exposure_params = m_client->getEnv().getLocalPlayer()->getLighting().exposure; + const auto &lighting = m_client->getEnv().getLocalPlayer()->getLighting(); + + const AutoExposure &exposure_params = lighting.exposure; std::array exposure_buffer = { std::pow(2.0f, exposure_params.luminance_min), std::pow(2.0f, exposure_params.luminance_max), @@ -524,12 +511,14 @@ public: m_exposure_params_pixel.set(exposure_buffer.data(), services); if (m_bloom_enabled) { - m_bloom_intensity_pixel.set(&m_bloom_intensity, services); - m_bloom_radius_pixel.set(&m_bloom_radius, services); - m_bloom_strength_pixel.set(&m_bloom_strength, services); + float intensity = std::max(lighting.bloom_intensity, 0.0f); + m_bloom_intensity_pixel.set(&intensity, services); + float strength_factor = std::max(lighting.bloom_strength_factor, 0.0f); + m_bloom_strength_pixel.set(&strength_factor, services); + float radius = std::max(lighting.bloom_radius, 0.0f); + m_bloom_radius_pixel.set(&radius, services); } - const auto &lighting = m_client->getEnv().getLocalPlayer()->getLighting(); float saturation = lighting.saturation; m_saturation_pixel.set(&saturation, services); diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index c4933e062..b709fc7bf 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -41,7 +41,6 @@ with this program; if not, write to the Free Software Foundation, Inc., RenderingEngine *RenderingEngine::s_singleton = nullptr; const video::SColor RenderingEngine::MENU_SKY_COLOR = video::SColor(255, 140, 186, 250); -const float RenderingEngine::BASE_BLOOM_STRENGTH = 1.0f; /* Helper classes */ diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 7f7518f61..5f6890c8b 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -81,7 +81,6 @@ class RenderingEngine { public: static const video::SColor MENU_SKY_COLOR; - static const float BASE_BLOOM_STRENGTH; RenderingEngine(IEventReceiver *eventReceiver); ~RenderingEngine(); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 12946b06d..ae9180e72 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -332,9 +332,6 @@ void set_default_settings() settings->setDefault("antialiasing", "none"); settings->setDefault("enable_bloom", "false"); settings->setDefault("enable_bloom_debug", "false"); - settings->setDefault("bloom_strength_factor", "1.0"); - settings->setDefault("bloom_intensity", "0.05"); - settings->setDefault("bloom_radius", "1"); settings->setDefault("enable_volumetric_lighting", "false"); settings->setDefault("enable_water_reflections", "false"); settings->setDefault("enable_translucent_foliage", "false"); diff --git a/src/lighting.h b/src/lighting.h index fbf10b1c9..b0ba714b9 100644 --- a/src/lighting.h +++ b/src/lighting.h @@ -57,4 +57,7 @@ struct Lighting float saturation {1.0f}; float volumetric_light_strength {0.0f}; video::SColor shadow_tint {255, 0, 0, 0}; + float bloom_intensity {0.05f}; + float bloom_strength_factor {1.0f}; + float bloom_radius {1.0f}; }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 2716879f4..725b6a5c7 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1819,4 +1819,9 @@ void Client::handleCommand_SetLighting(NetworkPacket *pkt) *pkt >> lighting.volumetric_light_strength; if (pkt->getRemainingBytes() >= 4) *pkt >> lighting.shadow_tint; + if (pkt->getRemainingBytes() >= 12) { + *pkt >> lighting.bloom_intensity + >> lighting.bloom_strength_factor + >> lighting.bloom_radius; + } } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index a11308a2e..b9ea0a4e4 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2649,6 +2649,14 @@ int ObjectRef::l_set_lighting(lua_State *L) lighting.volumetric_light_strength = rangelim(lighting.volumetric_light_strength, 0.0f, 1.0f); } lua_pop(L, 1); // volumetric_light + + lua_getfield(L, 2, "bloom"); + if (lua_istable(L, -1)) { + lighting.bloom_intensity = getfloatfield_default(L, -1, "intensity", lighting.bloom_intensity); + lighting.bloom_strength_factor = getfloatfield_default(L, -1, "strength_factor", lighting.bloom_strength_factor); + lighting.bloom_radius = getfloatfield_default(L, -1, "radius", lighting.bloom_radius); + } + lua_pop(L, 1); // bloom } getServer(L)->setLighting(player, lighting); @@ -2693,6 +2701,14 @@ int ObjectRef::l_get_lighting(lua_State *L) lua_pushnumber(L, lighting.volumetric_light_strength); lua_setfield(L, -2, "strength"); lua_setfield(L, -2, "volumetric_light"); + lua_newtable(L); // "bloom" + lua_pushnumber(L, lighting.bloom_intensity); + lua_setfield(L, -2, "intensity"); + lua_pushnumber(L, lighting.bloom_strength_factor); + lua_setfield(L, -2, "strength_factor"); + lua_pushnumber(L, lighting.bloom_radius); + lua_setfield(L, -2, "radius"); + lua_setfield(L, -2, "bloom"); return 1; } diff --git a/src/server.cpp b/src/server.cpp index e4fecf7c1..037857b21 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1919,6 +1919,8 @@ void Server::SendSetLighting(session_t peer_id, const Lighting &lighting) << lighting.exposure.center_weight_power; pkt << lighting.volumetric_light_strength << lighting.shadow_tint; + pkt << lighting.bloom_intensity << lighting.bloom_strength_factor << + lighting.bloom_radius; Send(&pkt); } From 07ff2a5c016bd0fbfed1ac7b3172a872eab1317e Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 9 Oct 2024 15:08:15 +0200 Subject: [PATCH 165/200] ContentDB dialog: React to window info changes immediately (#15248) --- builtin/mainmenu/content/dlg_contentdb.lua | 5 +++++ builtin/mainmenu/content/dlg_package.lua | 13 ++++++++++++- src/gui/guiEngine.cpp | 7 +++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/builtin/mainmenu/content/dlg_contentdb.lua b/builtin/mainmenu/content/dlg_contentdb.lua index a86815b77..8f232e490 100644 --- a/builtin/mainmenu/content/dlg_contentdb.lua +++ b/builtin/mainmenu/content/dlg_contentdb.lua @@ -496,6 +496,11 @@ local function handle_events(event) return true end + if event == "WindowInfoChange" then + ui.update() + return true + end + return false end diff --git a/builtin/mainmenu/content/dlg_package.lua b/builtin/mainmenu/content/dlg_package.lua index 5b9db4860..404e950c4 100644 --- a/builtin/mainmenu/content/dlg_package.lua +++ b/builtin/mainmenu/content/dlg_package.lua @@ -305,12 +305,23 @@ local function handle_submit(this, fields) end +local function handle_events(event) + if event == "WindowInfoChange" then + ui.update() + return true + end + + return false +end + + function create_package_dialog(package) assert(package) local dlg = dialog_create("package_dialog_" .. package.id, get_formspec, - handle_submit) + handle_submit, + handle_events) local data = dlg.data data.package = package diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 4a3d53f51..8a4e22b1d 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/renderingengine.h" #include "client/shader.h" #include "client/tile.h" +#include "clientdynamicinfo.h" #include "config.h" #include "content/content.h" #include "content/mods.h" @@ -316,6 +317,7 @@ void GUIEngine::run() ); const bool initial_window_maximized = !g_settings->getBool("fullscreen") && g_settings->getBool("window_maximized"); + auto last_window_info = ClientDynamicInfo::getCurrent(); FpsControl fps_control; f32 dtime = 0.0f; @@ -335,6 +337,11 @@ void GUIEngine::run() updateTopLeftTextSize(); text_height = g_fontengine->getTextHeight(); } + auto window_info = ClientDynamicInfo::getCurrent(); + if (!window_info.equal(last_window_info)) { + m_script->handleMainMenuEvent("WindowInfoChange"); + last_window_info = window_info; + } driver->beginScene(true, true, RenderingEngine::MENU_SKY_COLOR); From 3a7c8279bf1fdfdd64bc8b944f11411332ae807c Mon Sep 17 00:00:00 2001 From: paradust7 <102263465+paradust7@users.noreply.github.com> Date: Wed, 9 Oct 2024 07:24:44 -0700 Subject: [PATCH 166/200] Split log.h to speed up compilation (#15258) --- src/client/game.cpp | 1 + src/client/inputhandler.cpp | 1 + src/client/sound/ogg_file.cpp | 1 + src/client/sound/sound_singleton.h | 1 + src/craftdef.cpp | 1 + src/filesys.cpp | 1 + src/log.cpp | 3 +- src/log.h | 201 +---------------------------- src/log_internal.h | 189 +++++++++++++++++++++++++++ src/main.cpp | 1 + src/pathfinder.cpp | 2 + src/script/lua_api/l_util.cpp | 1 + src/serialization.cpp | 1 + src/terminal_chat_console.h | 1 + src/texture_override.cpp | 1 + src/threading/thread.cpp | 2 +- src/unittest/test.cpp | 1 + src/util/colorize.cpp | 1 + 18 files changed, 207 insertions(+), 203 deletions(-) create mode 100644 src/log_internal.h diff --git a/src/client/game.cpp b/src/client/game.cpp index f1e798a52..76e9194ec 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -43,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/touchcontrols.h" #include "itemdef.h" #include "log.h" +#include "log_internal.h" #include "filesys.h" #include "gameparams.h" #include "gettext.h" diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 168ef1193..6517ad582 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/mainmenumanager.h" #include "gui/touchcontrols.h" #include "hud.h" +#include "log_internal.h" void KeyCache::populate_nonchanging() { diff --git a/src/client/sound/ogg_file.cpp b/src/client/sound/ogg_file.cpp index 11659c706..660dfdf94 100644 --- a/src/client/sound/ogg_file.cpp +++ b/src/client/sound/ogg_file.cpp @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include // memcpy +#include namespace sound { diff --git a/src/client/sound/sound_singleton.h b/src/client/sound/sound_singleton.h index 32cd2d4f8..10ecc0d96 100644 --- a/src/client/sound/sound_singleton.h +++ b/src/client/sound/sound_singleton.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include #include "al_helpers.h" namespace sound { diff --git a/src/craftdef.cpp b/src/craftdef.cpp index 72b8e8f9d..611632579 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include #include "gamedef.h" #include "inventory.h" #include "util/serialize.h" diff --git a/src/filesys.cpp b/src/filesys.cpp index 4287c8b05..196aca080 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include #include "log.h" #include "config.h" #include "porting.h" diff --git a/src/log.cpp b/src/log.cpp index f7eb691ac..5fac64f5c 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -17,7 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "log.h" +#include "log_internal.h" #include "threading/mutex_auto_lock.h" #include "debug.h" @@ -27,7 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "config.h" #include "exceptions.h" #include "util/numeric.h" -#include "log.h" #include "filesys.h" #ifdef __ANDROID__ diff --git a/src/log.h b/src/log.h index 721ce58ed..ccd13acf3 100644 --- a/src/log.h +++ b/src/log.h @@ -1,198 +1,9 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ +// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -#include -#include -#include -#include -#include -#include -#include -#include "threading/mutex_auto_lock.h" #include "util/basic_macros.h" #include "util/stream.h" -#include "irrlichttypes.h" - -class ILogOutput; - -enum LogLevel { - LL_NONE, // Special level that is always printed - LL_ERROR, - LL_WARNING, - LL_ACTION, // In-game actions - LL_INFO, - LL_VERBOSE, - LL_TRACE, - LL_MAX, -}; - -enum LogColor { - LOG_COLOR_NEVER, - LOG_COLOR_ALWAYS, - LOG_COLOR_AUTO, -}; - -typedef u8 LogLevelMask; -#define LOGLEVEL_TO_MASKLEVEL(x) (1 << x) - -class Logger { -public: - void addOutput(ILogOutput *out); - void addOutput(ILogOutput *out, LogLevel lev); - void addOutputMasked(ILogOutput *out, LogLevelMask mask); - void addOutputMaxLevel(ILogOutput *out, LogLevel lev); - LogLevelMask removeOutput(ILogOutput *out); - void setLevelSilenced(LogLevel lev, bool silenced); - - void registerThread(std::string_view name); - void deregisterThread(); - - void log(LogLevel lev, std::string_view text); - // Logs without a prefix - void logRaw(LogLevel lev, std::string_view text); - - static LogLevel stringToLevel(std::string_view name); - static const char *getLevelLabel(LogLevel lev); - - bool hasOutput(LogLevel level) { - return m_has_outputs[level].load(std::memory_order_relaxed); - } - - bool isLevelSilenced(LogLevel level) { - return m_silenced_levels[level].load(std::memory_order_relaxed); - } - - static LogColor color_mode; - -private: - void logToOutputsRaw(LogLevel, std::string_view line); - void logToOutputs(LogLevel, const std::string &combined, - const std::string &time, const std::string &thread_name, - std::string_view payload_text); - - const std::string &getThreadName(); - - std::vector m_outputs[LL_MAX]; - std::atomic m_has_outputs[LL_MAX]; - std::atomic m_silenced_levels[LL_MAX]; - std::map m_thread_names; - mutable std::mutex m_mutex; -}; - -class ILogOutput { -public: - virtual void logRaw(LogLevel, std::string_view line) = 0; - virtual void log(LogLevel, const std::string &combined, - const std::string &time, const std::string &thread_name, - std::string_view payload_text) = 0; -}; - -class ICombinedLogOutput : public ILogOutput { -public: - void log(LogLevel lev, const std::string &combined, - const std::string &time, const std::string &thread_name, - std::string_view payload_text) - { - logRaw(lev, combined); - } -}; - -class StreamLogOutput : public ICombinedLogOutput { -public: - StreamLogOutput(std::ostream &stream); - - void logRaw(LogLevel lev, std::string_view line); - -private: - std::ostream &m_stream; - bool is_tty = false; -}; - -class FileLogOutput : public ICombinedLogOutput { -public: - void setFile(const std::string &filename, s64 file_size_max); - - void logRaw(LogLevel lev, std::string_view line) - { - m_stream << line << std::endl; - } - -private: - std::ofstream m_stream; -}; - -class LogOutputBuffer : public ICombinedLogOutput { -public: - LogOutputBuffer(Logger &logger) : - m_logger(logger) - { - updateLogLevel(); - }; - - virtual ~LogOutputBuffer() - { - m_logger.removeOutput(this); - } - - void updateLogLevel(); - - void logRaw(LogLevel lev, std::string_view line); - - void clear() - { - MutexAutoLock lock(m_buffer_mutex); - m_buffer = std::queue(); - } - - bool empty() const - { - MutexAutoLock lock(m_buffer_mutex); - return m_buffer.empty(); - } - - std::string get() - { - MutexAutoLock lock(m_buffer_mutex); - if (m_buffer.empty()) - return ""; - std::string s = std::move(m_buffer.front()); - m_buffer.pop(); - return s; - } - -private: - // g_logger serializes calls to logRaw() with a mutex, but that - // doesn't prevent get() / clear() from being called on top of it. - // This mutex prevents that. - mutable std::mutex m_buffer_mutex; - std::queue m_buffer; - Logger &m_logger; -}; - -#ifdef __ANDROID__ -class AndroidLogOutput : public ICombinedLogOutput { -public: - void logRaw(LogLevel lev, std::string_view line); -}; -#endif /* * LogTarget @@ -325,16 +136,6 @@ private: }; -#ifdef __ANDROID__ -extern AndroidLogOutput stdout_output; -extern AndroidLogOutput stderr_output; -#else -extern StreamLogOutput stdout_output; -extern StreamLogOutput stderr_output; -#endif - -extern Logger g_logger; - /* * By making the streams thread_local, each thread has its own * private buffer. Two or more threads can write to the same stream diff --git a/src/log_internal.h b/src/log_internal.h new file mode 100644 index 000000000..c8bc1b310 --- /dev/null +++ b/src/log_internal.h @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "threading/mutex_auto_lock.h" +#include "util/basic_macros.h" +#include "util/stream.h" +#include "irrlichttypes.h" +#include "log.h" + +class ILogOutput; + +enum LogLevel { + LL_NONE, // Special level that is always printed + LL_ERROR, + LL_WARNING, + LL_ACTION, // In-game actions + LL_INFO, + LL_VERBOSE, + LL_TRACE, + LL_MAX, +}; + +enum LogColor { + LOG_COLOR_NEVER, + LOG_COLOR_ALWAYS, + LOG_COLOR_AUTO, +}; + +typedef u8 LogLevelMask; +#define LOGLEVEL_TO_MASKLEVEL(x) (1 << x) + +class Logger { +public: + void addOutput(ILogOutput *out); + void addOutput(ILogOutput *out, LogLevel lev); + void addOutputMasked(ILogOutput *out, LogLevelMask mask); + void addOutputMaxLevel(ILogOutput *out, LogLevel lev); + LogLevelMask removeOutput(ILogOutput *out); + void setLevelSilenced(LogLevel lev, bool silenced); + + void registerThread(std::string_view name); + void deregisterThread(); + + void log(LogLevel lev, std::string_view text); + // Logs without a prefix + void logRaw(LogLevel lev, std::string_view text); + + static LogLevel stringToLevel(std::string_view name); + static const char *getLevelLabel(LogLevel lev); + + bool hasOutput(LogLevel level) { + return m_has_outputs[level].load(std::memory_order_relaxed); + } + + bool isLevelSilenced(LogLevel level) { + return m_silenced_levels[level].load(std::memory_order_relaxed); + } + + static LogColor color_mode; + +private: + void logToOutputsRaw(LogLevel, std::string_view line); + void logToOutputs(LogLevel, const std::string &combined, + const std::string &time, const std::string &thread_name, + std::string_view payload_text); + + const std::string &getThreadName(); + + std::vector m_outputs[LL_MAX]; + std::atomic m_has_outputs[LL_MAX]; + std::atomic m_silenced_levels[LL_MAX]; + std::map m_thread_names; + mutable std::mutex m_mutex; +}; + +class ILogOutput { +public: + virtual void logRaw(LogLevel, std::string_view line) = 0; + virtual void log(LogLevel, const std::string &combined, + const std::string &time, const std::string &thread_name, + std::string_view payload_text) = 0; +}; + +class ICombinedLogOutput : public ILogOutput { +public: + void log(LogLevel lev, const std::string &combined, + const std::string &time, const std::string &thread_name, + std::string_view payload_text) + { + logRaw(lev, combined); + } +}; + +class StreamLogOutput : public ICombinedLogOutput { +public: + StreamLogOutput(std::ostream &stream); + + void logRaw(LogLevel lev, std::string_view line); + +private: + std::ostream &m_stream; + bool is_tty = false; +}; + +class FileLogOutput : public ICombinedLogOutput { +public: + void setFile(const std::string &filename, s64 file_size_max); + + void logRaw(LogLevel lev, std::string_view line) + { + m_stream << line << std::endl; + } + +private: + std::ofstream m_stream; +}; + +class LogOutputBuffer : public ICombinedLogOutput { +public: + LogOutputBuffer(Logger &logger) : + m_logger(logger) + { + updateLogLevel(); + }; + + virtual ~LogOutputBuffer() + { + m_logger.removeOutput(this); + } + + void updateLogLevel(); + + void logRaw(LogLevel lev, std::string_view line); + + void clear() + { + MutexAutoLock lock(m_buffer_mutex); + m_buffer = std::queue(); + } + + bool empty() const + { + MutexAutoLock lock(m_buffer_mutex); + return m_buffer.empty(); + } + + std::string get() + { + MutexAutoLock lock(m_buffer_mutex); + if (m_buffer.empty()) + return ""; + std::string s = std::move(m_buffer.front()); + m_buffer.pop(); + return s; + } + +private: + // g_logger serializes calls to logRaw() with a mutex, but that + // doesn't prevent get() / clear() from being called on top of it. + // This mutex prevents that. + mutable std::mutex m_buffer_mutex; + std::queue m_buffer; + Logger &m_logger; +}; + +#ifdef __ANDROID__ +class AndroidLogOutput : public ICombinedLogOutput { +public: + void logRaw(LogLevel lev, std::string_view line); +}; +#endif + +#ifdef __ANDROID__ +extern AndroidLogOutput stdout_output; +extern AndroidLogOutput stderr_output; +#else +extern StreamLogOutput stdout_output; +extern StreamLogOutput stderr_output; +#endif + +extern Logger g_logger; diff --git a/src/main.cpp b/src/main.cpp index 1e717bfd1..803f3c6b0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "migratesettings.h" #include "gettext.h" #include "log.h" +#include "log_internal.h" #include "util/quicktune.h" #include "httpfetch.h" #include "gameparams.h" diff --git a/src/pathfinder.cpp b/src/pathfinder.cpp index 5420431f5..8b90a139c 100644 --- a/src/pathfinder.cpp +++ b/src/pathfinder.cpp @@ -40,6 +40,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #endif +#include + /******************************************************************************/ /* Typedefs and macros */ /******************************************************************************/ diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 45a447cb3..c899e55f4 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "convert_json.h" #include "debug.h" #include "log.h" +#include "log_internal.h" #include "tool.h" #include "filesys.h" #include "settings.h" diff --git a/src/serialization.cpp b/src/serialization.cpp index 0319b0159..d35e0f23f 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#include /* report a zlib or i/o error */ static void zerr(int ret) diff --git a/src/terminal_chat_console.h b/src/terminal_chat_console.h index 1bd226609..7ce2c5c2b 100644 --- a/src/terminal_chat_console.h +++ b/src/terminal_chat_console.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "threading/thread.h" #include "util/container.h" #include "log.h" +#include "log_internal.h" #include #include diff --git a/src/texture_override.cpp b/src/texture_override.cpp index 1b8b4671d..e8386afe2 100644 --- a/src/texture_override.cpp +++ b/src/texture_override.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include #include +#include #define override_cast static_cast diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index 21143f231..f9e356ab7 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE. #include "threading/thread.h" #include "threading/mutex_auto_lock.h" -#include "log.h" +#include "log_internal.h" #include "porting.h" // for setName diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 33f8dcbb5..a3b9250a0 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "itemdef.h" #include "dummygamedef.h" +#include "log_internal.h" #include "modchannels.h" #include "util/numeric.h" #include "porting.h" diff --git a/src/util/colorize.cpp b/src/util/colorize.cpp index 873ec06fc..0814c2d34 100644 --- a/src/util/colorize.cpp +++ b/src/util/colorize.cpp @@ -23,6 +23,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "log.h" #include "string.h" #include +#include std::string colorize_url(const std::string &url) { From 87a42d62b29338a96b5bb18e1fb147d4aac04118 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 7 Sep 2024 12:49:37 +0200 Subject: [PATCH 167/200] Fix GLTF test depending on irrlicht internals & memory leaks Co-authored-by: Lars Mueller --- src/unittest/test_irr_gltf_mesh_loader.cpp | 40 +++++++++++++--------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/unittest/test_irr_gltf_mesh_loader.cpp b/src/unittest/test_irr_gltf_mesh_loader.cpp index 8ab57e590..847360be3 100644 --- a/src/unittest/test_irr_gltf_mesh_loader.cpp +++ b/src/unittest/test_irr_gltf_mesh_loader.cpp @@ -1,13 +1,12 @@ // Minetest // SPDX-License-Identifier: LGPL-2.1-or-later -#include "CSceneManager.h" #include "content/subgames.h" #include "filesys.h" -#include "CReadFile.h" #include "irr_v3d.h" #include "irr_v2d.h" +#include "irr_ptr.h" #include @@ -20,10 +19,16 @@ const auto gamespec = findSubgame("devtest"); if (!gamespec.isValid()) SKIP(); -irr::scene::CSceneManager smgr(nullptr, nullptr, nullptr); -const auto loadMesh = [&smgr](const irr::io::path& filepath) { - irr::io::CReadFile file(filepath); - return smgr.getMesh(&file); +irr::SIrrlichtCreationParameters p; +p.DriverType = video::EDT_NULL; +auto *driver = irr::createDeviceEx(p); +REQUIRE(driver); + +auto *smgr = driver->getSceneManager(); +const auto loadMesh = [&] (const io::path& filepath) { + irr_ptr file(driver->getFileSystem()->createAndOpenFile(filepath)); + REQUIRE(file); + return smgr->getMesh(file.get()); }; const static auto model_stem = gamespec.gamemods_path + @@ -33,21 +38,21 @@ SECTION("error cases") { const static auto invalid_model_path = gamespec.gamemods_path + DIR_DELIM + "gltf" + DIR_DELIM + "invalid" + DIR_DELIM; SECTION("empty gltf file") { - CHECK(loadMesh(invalid_model_path + "empty.gltf") == nullptr); + CHECK(!loadMesh(invalid_model_path + "empty.gltf")); } SECTION("null file pointer") { - CHECK(smgr.getMesh(nullptr) == nullptr); + CHECK(!smgr->getMesh(nullptr)); } SECTION("invalid JSON") { - CHECK(loadMesh(invalid_model_path + "json_missing_brace.gltf") == nullptr); + CHECK(!loadMesh(invalid_model_path + "json_missing_brace.gltf")); } // This is an example of something that should be validated by tiniergltf. SECTION("invalid bufferview bounds") { - CHECK(loadMesh(invalid_model_path + "invalid_bufferview_bounds.gltf") == nullptr); + CHECK(!loadMesh(invalid_model_path + "invalid_bufferview_bounds.gltf")); } } @@ -59,7 +64,7 @@ SECTION("minimal triangle") { model_stem + "triangle_without_indices.gltf"); INFO(path); const auto mesh = loadMesh(path); - REQUIRE(mesh != nullptr); + REQUIRE(mesh); REQUIRE(mesh->getMeshBufferCount() == 1); SECTION("vertex coordinates are correct") { @@ -83,7 +88,7 @@ SECTION("minimal triangle") { SECTION("blender cube") { const auto mesh = loadMesh(model_stem + "blender_cube.gltf"); - REQUIRE(mesh != nullptr); + REQUIRE(mesh); REQUIRE(mesh->getMeshBufferCount() == 1); SECTION("vertex coordinates are correct") { REQUIRE(mesh->getMeshBuffer(0)->getVertexCount() == 24); @@ -136,7 +141,7 @@ SECTION("blender cube") { SECTION("blender cube scaled") { const auto mesh = loadMesh(model_stem + "blender_cube_scaled.gltf"); - REQUIRE(mesh != nullptr); + REQUIRE(mesh); REQUIRE(mesh->getMeshBufferCount() == 1); SECTION("Scaling is correct") { @@ -157,7 +162,7 @@ SECTION("blender cube scaled") { SECTION("blender cube matrix transform") { const auto mesh = loadMesh(model_stem + "blender_cube_matrix_transform.gltf"); - REQUIRE(mesh != nullptr); + REQUIRE(mesh); REQUIRE(mesh->getMeshBufferCount() == 1); SECTION("Transformation is correct") { @@ -183,7 +188,7 @@ SECTION("blender cube matrix transform") { SECTION("snow man") { const auto mesh = loadMesh(model_stem + "snow_man.gltf"); - REQUIRE(mesh != nullptr); + REQUIRE(mesh); REQUIRE(mesh->getMeshBufferCount() == 3); SECTION("vertex coordinates are correct for all buffers") { @@ -338,7 +343,7 @@ SECTION("snow man") { SECTION("simple sparse accessor") { const auto mesh = loadMesh(model_stem + "simple_sparse_accessor.gltf"); - REQUIRE(mesh != nullptr); + REQUIRE(mesh); const auto *vertices = reinterpret_cast( mesh->getMeshBuffer(0)->getVertices()); const std::array expectedPositions = { @@ -363,4 +368,7 @@ SECTION("simple sparse accessor") CHECK(vertices[i].Pos == expectedPositions[i]); } +driver->closeDevice(); +driver->drop(); + } From 3c5f05b2847acd61470ae3ae781856e62cfbfbc2 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 7 Sep 2024 12:50:25 +0200 Subject: [PATCH 168/200] Don't expose irrlicht internal headers as public --- irr/{src => include}/KHR/khrplatform.h | 0 irr/src/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename irr/{src => include}/KHR/khrplatform.h (100%) diff --git a/irr/src/KHR/khrplatform.h b/irr/include/KHR/khrplatform.h similarity index 100% rename from irr/src/KHR/khrplatform.h rename to irr/include/KHR/khrplatform.h diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 22a0d0093..6e38220be 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -480,8 +480,8 @@ add_library(IrrlichtMt::IrrlichtMt ALIAS IrrlichtMt) target_include_directories(IrrlichtMt PUBLIC "$" - "$" PRIVATE + "$" ${link_includes} ) From 4952f17df4546e580dcc6033be433e47e4a6615e Mon Sep 17 00:00:00 2001 From: grorp Date: Sat, 28 Sep 2024 11:23:09 +0200 Subject: [PATCH 169/200] Auto-toggle TouchControls in-game when receiving touch/mouse input --- builtin/mainmenu/settings/dlg_settings.lua | 67 ++++++++++++---------- builtin/settingtypes.txt | 35 +++++------ irr/include/IEventReceiver.h | 8 +++ src/client/game.cpp | 20 ++++++- src/client/inputhandler.cpp | 26 +++++++++ src/client/inputhandler.h | 31 ++++------ src/client/renderingengine.cpp | 4 +- src/client/renderingengine.h | 10 +++- src/defaultsettings.cpp | 5 +- src/gui/guiFormSpecMenu.cpp | 2 +- src/gui/guiInventoryList.cpp | 3 +- src/gui/modalMenu.cpp | 9 +-- src/gui/modalMenu.h | 12 ---- src/gui/touchcontrols.cpp | 6 ++ src/gui/touchcontrols.h | 3 + 15 files changed, 152 insertions(+), 89 deletions(-) diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 182319be0..d668fba50 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -237,6 +237,12 @@ local function load() zh_CN = "中文 (简体) [zh_CN]", zh_TW = "正體中文 (繁體) [zh_TW]", } + + get_setting_info("touch_controls").option_labels = { + ["auto"] = fgettext_ne("Auto"), + ["true"] = fgettext_ne("Enabled"), + ["false"] = fgettext_ne("Disabled"), + } end @@ -336,9 +342,14 @@ local function check_requirements(name, requires) local video_driver = core.get_active_driver() local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2" + local touch_controls = core.settings:get("touch_controls") local special = { android = PLATFORM == "Android", desktop = PLATFORM ~= "Android", + -- When touch_controls is "auto", we don't which input method will be used, + -- so we show settings for both. + touchscreen = touch_controls == "auto" or core.is_yes(touch_controls), + keyboard_mouse = touch_controls == "auto" or not core.is_yes(touch_controls), shaders_support = shaders_support, shaders = core.settings:get_bool("enable_shaders") and shaders_support, opengl = video_driver == "opengl", @@ -624,6 +635,18 @@ function write_settings_early() end end +local function regenerate_page_list(dialogdata) + local suggested_page_id = update_filtered_pages(dialogdata.query) + + dialogdata.components = nil + + if not filtered_page_by_id[dialogdata.page_id] then + dialogdata.leftscroll = 0 + dialogdata.rightscroll = 0 + + dialogdata.page_id = suggested_page_id + end +end local function buttonhandler(this, fields) local dialogdata = this.data @@ -648,27 +671,7 @@ local function buttonhandler(this, fields) local value = core.is_yes(fields.show_advanced) core.settings:set_bool("show_advanced", value) write_settings_early() - end - - -- touch_controls is a checkbox in a setting component. We handle this - -- setting differently so we can hide/show pages using the next if-statement - if fields.touch_controls ~= nil then - local value = core.is_yes(fields.touch_controls) - core.settings:set_bool("touch_controls", value) - write_settings_early() - end - - if fields.show_advanced ~= nil or fields.touch_controls ~= nil then - local suggested_page_id = update_filtered_pages(dialogdata.query) - - dialogdata.components = nil - - if not filtered_page_by_id[dialogdata.page_id] then - dialogdata.leftscroll = 0 - dialogdata.rightscroll = 0 - - dialogdata.page_id = suggested_page_id - end + regenerate_page_list(dialogdata) return true end @@ -701,20 +704,26 @@ local function buttonhandler(this, fields) end end - for i, comp in ipairs(dialogdata.components) do - if comp.on_submit and comp:on_submit(fields, this) then - write_settings_early() - + local function after_setting_change(comp) + write_settings_early() + if comp.setting.name == "touch_controls" then + -- Changing the "touch_controls" setting may result in a different + -- page list. + regenerate_page_list(dialogdata) + else -- Clear components so they regenerate dialogdata.components = nil + end + end + + for i, comp in ipairs(dialogdata.components) do + if comp.on_submit and comp:on_submit(fields, this) then + after_setting_change(comp) return true end if comp.setting and fields["reset_" .. i] then core.settings:remove(comp.setting.name) - write_settings_early() - - -- Clear components so they regenerate - dialogdata.components = nil + after_setting_change(comp) return true end end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 2f7af3fae..1813a6cdf 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -61,7 +61,7 @@ # # # This is a comment # # -# # Requires: shaders, enable_dynamic_shadows, !touch_controls +# # Requires: shaders, enable_dynamic_shadows, !enable_waving_leaves # name (Readable name) type type_args # # A requirement can be the name of a boolean setting or an engine-defined value. @@ -72,6 +72,7 @@ # * shaders_support (a video driver that supports shaders, may not be enabled) # * shaders (both enable_shaders and shaders_support) # * desktop / android +# * touchscreen / keyboard_mouse # * opengl / gles # * You can negate any requirement by prepending with ! # @@ -91,7 +92,7 @@ camera_smoothing (Camera smoothing) float 0.0 0.0 0.99 # Smooths rotation of camera when in cinematic mode, 0 to disable. Enter cinematic mode by using the key set in Controls. # -# Requires: !touch_controls +# Requires: keyboard_mouse cinematic_camera_smoothing (Camera smoothing in cinematic mode) float 0.7 0.0 0.99 # If enabled, you can place nodes at the position (feet + eye level) where you stand. @@ -112,8 +113,8 @@ always_fly_fast (Always fly fast) bool true # The time in seconds it takes between repeated node placements when holding # the place button. # -# Requires: !touch_controls -repeat_place_time (Place repetition interval) float 0.25 0.16 2.0 +# Requires: keyboard_mouse +repeat_place_time (Place repetition interval) float 0.25 0.15 2.0 # The minimum time in seconds it takes between digging nodes when holding # the dig button. @@ -131,60 +132,62 @@ safe_dig_and_place (Safe digging and placing) bool false # Invert vertical mouse movement. # -# Requires: !touch_controls +# Requires: keyboard_mouse invert_mouse (Invert mouse) bool false # Mouse sensitivity multiplier. # -# Requires: !touch_controls +# Requires: keyboard_mouse mouse_sensitivity (Mouse sensitivity) float 0.2 0.001 10.0 # Enable mouse wheel (scroll) for item selection in hotbar. # -# Requires: !touch_controls +# Requires: keyboard_mouse enable_hotbar_mouse_wheel (Hotbar: Enable mouse wheel for selection) bool true # Invert mouse wheel (scroll) direction for item selection in hotbar. # -# Requires: !touch_controls +# Requires: keyboard_mouse invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false [*Touchscreen] # Enables the touchscreen controls, allowing you to play the game with a touchscreen. -touch_controls (Enable touchscreen controls) bool true +# "auto" means that the touchscreen controls will be enabled and disabled +# automatically depending on the last used input method. +touch_controls (Touchscreen controls) enum auto auto,true,false # Touchscreen sensitivity multiplier. # -# Requires: touch_controls +# Requires: touchscreen touchscreen_sensitivity (Touchscreen sensitivity) float 0.2 0.001 10.0 # The length in pixels after which a touch interaction is considered movement. # -# Requires: touch_controls +# Requires: touchscreen touchscreen_threshold (Movement threshold) int 20 0 100 # The delay in milliseconds after which a touch interaction is considered a long tap. # -# Requires: touch_controls +# Requires: touchscreen touch_long_tap_delay (Threshold for long taps) int 400 100 1000 # Use crosshair to select object instead of whole screen. # If enabled, a crosshair will be shown and will be used for selecting object. # -# Requires: touch_controls +# Requires: touchscreen touch_use_crosshair (Use crosshair for touch screen) bool false # Fixes the position of virtual joystick. # If disabled, virtual joystick will center to first-touch's position. # -# Requires: touch_controls +# Requires: touchscreen fixed_virtual_joystick (Fixed virtual joystick) bool false # Use virtual joystick to trigger "Aux1" button. # If enabled, virtual joystick will also tap "Aux1" button when out of main circle. # -# Requires: touch_controls +# Requires: touchscreen virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false # The gesture for punching players/entities. @@ -197,7 +200,7 @@ virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool fals # Known from the classic Minetest mobile controls. # Combat is more or less impossible. # -# Requires: touch_controls +# Requires: touchscreen touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap diff --git a/irr/include/IEventReceiver.h b/irr/include/IEventReceiver.h index a484bfb84..7fb9e5f4e 100644 --- a/irr/include/IEventReceiver.h +++ b/irr/include/IEventReceiver.h @@ -347,6 +347,9 @@ struct SEvent //! Type of mouse event EMOUSE_INPUT_EVENT Event; + + //! Is this a simulated mouse event generated by Minetest itself? + bool Simulated; }; //! Any kind of keyboard event. @@ -538,6 +541,11 @@ struct SEvent struct SUserEvent UserEvent; struct SApplicationEvent ApplicationEvent; }; + + SEvent() { + // would be left uninitialized in many places otherwise + MouseInput.Simulated = false; + } }; //! Interface of an object which can receive events. diff --git a/src/client/game.cpp b/src/client/game.cpp index 76e9194ec..090af05c9 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -723,6 +723,7 @@ protected: void processUserInput(f32 dtime); void processKeyInput(); void processItemSelection(u16 *new_playeritem); + bool shouldShowTouchControls(); void dropSelectedItem(bool single_item = false); void openInventory(); @@ -1565,6 +1566,14 @@ bool Game::createClient(const GameStartData &start_data) return true; } +bool Game::shouldShowTouchControls() +{ + const std::string &touch_controls = g_settings->get("touch_controls"); + if (touch_controls == "auto") + return RenderingEngine::getLastPointerType() == PointerType::Touch; + return is_yes(touch_controls); +} + bool Game::initGui() { m_game_ui->init(); @@ -1579,7 +1588,7 @@ bool Game::initGui() gui_chat_console = make_irr(guienv, guienv->getRootGUIElement(), -1, chat_backend, client, &g_menumgr); - if (g_settings->getBool("touch_controls")) { + if (shouldShowTouchControls()) { g_touchcontrols = new TouchControls(device, texture_src); g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); } @@ -2031,6 +2040,15 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times, void Game::processUserInput(f32 dtime) { + bool desired = shouldShowTouchControls(); + if (desired && !g_touchcontrols) { + g_touchcontrols = new TouchControls(device, texture_src); + + } else if (!desired && g_touchcontrols) { + delete g_touchcontrols; + g_touchcontrols = nullptr; + } + // Reset input if window not active or some menu is active if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console.get())) { if (m_game_focused) { diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 6517ad582..2ce058ff4 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/touchcontrols.h" #include "hud.h" #include "log_internal.h" +#include "client/renderingengine.h" void KeyCache::populate_nonchanging() { @@ -142,6 +143,11 @@ bool MyEventReceiver::OnEvent(const SEvent &event) } } + if (event.EventType == EET_MOUSE_INPUT_EVENT && !event.MouseInput.Simulated) + last_pointer_type = PointerType::Mouse; + else if (event.EventType == EET_TOUCH_INPUT_EVENT) + last_pointer_type = PointerType::Touch; + // Let the menu handle events, if one is active. if (isMenuActive()) { if (g_touchcontrols) @@ -237,6 +243,26 @@ float RealInputHandler::getJoystickDirection() return joystick.getMovementDirection(); } +v2s32 RealInputHandler::getMousePos() +{ + auto control = RenderingEngine::get_raw_device()->getCursorControl(); + if (control) { + return control->getPosition(); + } + + return m_mousepos; +} + +void RealInputHandler::setMousePos(s32 x, s32 y) +{ + auto control = RenderingEngine::get_raw_device()->getCursorControl(); + if (control) { + control->setPosition(x, y); + } else { + m_mousepos = v2s32(x, y); + } +} + /* * RandomInputHandler */ diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 8efefce5b..ee76151f4 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -23,10 +23,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "joystick_controller.h" #include #include "keycode.h" -#include "renderingengine.h" class InputHandler; +enum class PointerType { + Mouse, + Touch, +}; + /**************************************************************************** Fast key cache for main game loop ****************************************************************************/ @@ -199,6 +203,8 @@ public: JoystickController *joystick = nullptr; + PointerType getLastPointerType() { return last_pointer_type; } + private: s32 mouse_wheel = 0; @@ -223,6 +229,8 @@ private: // Intentionally not reset by clearInput/releaseAllKeys. bool fullscreen_is_down = false; + + PointerType last_pointer_type = PointerType::Mouse; }; class InputHandler @@ -331,25 +339,8 @@ public: m_receiver->dontListenForKeys(); } - virtual v2s32 getMousePos() - { - auto control = RenderingEngine::get_raw_device()->getCursorControl(); - if (control) { - return control->getPosition(); - } - - return m_mousepos; - } - - virtual void setMousePos(s32 x, s32 y) - { - auto control = RenderingEngine::get_raw_device()->getCursorControl(); - if (control) { - control->setPosition(x, y); - } else { - m_mousepos = v2s32(x, y); - } - } + virtual v2s32 getMousePos(); + virtual void setMousePos(s32 x, s32 y); virtual s32 getMouseWheel() { diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index b709fc7bf..f0d2abddb 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -172,7 +172,7 @@ static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std /* RenderingEngine class */ -RenderingEngine::RenderingEngine(IEventReceiver *receiver) +RenderingEngine::RenderingEngine(MyEventReceiver *receiver) { sanity_check(!s_singleton); @@ -225,6 +225,8 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) // This changes the minimum allowed number of vertices in a VBO. Default is 500. driver->setMinHardwareBufferVertexCount(4); + m_receiver = receiver; + s_singleton = this; g_settings->registerChangedCallback("fullscreen", settingChangedCallback, this); diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 5f6890c8b..ffdda636c 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "client/inputhandler.h" #include "irrlichttypes_extrabloated.h" #include "debug.h" #include "client/shader.h" @@ -82,7 +83,7 @@ class RenderingEngine public: static const video::SColor MENU_SKY_COLOR; - RenderingEngine(IEventReceiver *eventReceiver); + RenderingEngine(MyEventReceiver *eventReceiver); ~RenderingEngine(); void setResizable(bool resize); @@ -167,6 +168,12 @@ public: const irr::core::dimension2d initial_screen_size, const bool initial_window_maximized); + static PointerType getLastPointerType() + { + sanity_check(s_singleton && s_singleton->m_receiver); + return s_singleton->m_receiver->getLastPointerType(); + } + private: static void settingChangedCallback(const std::string &name, void *data); v2u32 _getWindowSize() const; @@ -174,5 +181,6 @@ private: std::unique_ptr core; irr::IrrlichtDevice *m_device = nullptr; irr::video::IVideoDriver *driver; + MyEventReceiver *m_receiver = nullptr; static RenderingEngine *s_singleton; }; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index ae9180e72..f1756bd8e 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -97,7 +97,10 @@ void set_default_settings() // Client settings->setDefault("address", ""); settings->setDefault("enable_sound", "true"); - settings->setDefault("touch_controls", bool_to_cstr(has_touch)); + settings->setDefault("touch_controls", "auto"); + // Since GUI scaling shouldn't suddenly change during a session, we use + // hardware detection for "touch_gui" instead of switching based on the last + // input method used. settings->setDefault("touch_gui", bool_to_cstr(has_touch)); settings->setDefault("sound_volume", "0.8"); settings->setDefault("sound_volume_unfocused", "0.3"); diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index c9366f83f..efd7b7e8c 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3615,7 +3615,7 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, int tooltip_offset_x = m_btn_height; int tooltip_offset_y = m_btn_height; - if (m_pointer_type == PointerType::Touch) { + if (RenderingEngine::getLastPointerType() == PointerType::Touch) { tooltip_offset_x *= 3; tooltip_offset_y = 0; if (m_pointer.X > (s32)screenSize.X / 2) diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp index e5ed6e6ef..1dd36bfc9 100644 --- a/src/gui/guiInventoryList.cpp +++ b/src/gui/guiInventoryList.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiFormSpecMenu.h" #include "client/hud.h" #include "client/client.h" +#include "client/renderingengine.h" #include GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env, @@ -154,7 +155,7 @@ void GUIInventoryList::draw() // Add hovering tooltip bool show_tooltip = !item.empty() && hovering && !selected_item; // Make it possible to see item tooltips on touchscreens - if (m_fs_menu->getPointerType() == PointerType::Touch) { + if (RenderingEngine::getLastPointerType() == PointerType::Touch) { show_tooltip |= hovering && selected && m_fs_menu->getSelectedAmount() != 0; } if (show_tooltip) { diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index fd60d08c2..ad4839170 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -187,6 +187,7 @@ bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool secon mouse_event.EventType = EET_MOUSE_INPUT_EVENT; mouse_event.MouseInput.X = m_pointer.X; mouse_event.MouseInput.Y = m_pointer.Y; + mouse_event.MouseInput.Simulated = true; switch (touch_event) { case ETIE_PRESSED_DOWN: mouse_event.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; @@ -210,7 +211,6 @@ bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool secon } bool retval; - m_simulated_mouse = true; do { if (preprocessEvent(mouse_event)) { retval = true; @@ -222,7 +222,6 @@ bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool secon } retval = target->OnEvent(mouse_event); } while (false); - m_simulated_mouse = false; if (!retval && !second_try) return simulateMouseEvent(touch_event, true); @@ -330,7 +329,6 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) holder.grab(this); // keep this alive until return (it might be dropped downstream [?]) if (event.TouchInput.touchedCount == 1) { - m_pointer_type = PointerType::Touch; m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y); gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(core::position2d(m_pointer)); @@ -373,9 +371,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) } if (event.EventType == EET_MOUSE_INPUT_EVENT) { - if (!m_simulated_mouse) { - // Only set the pointer type to mouse if this is a real mouse event. - m_pointer_type = PointerType::Mouse; + if (!event.MouseInput.Simulated) { + // Only process if this is a real mouse event. m_pointer = v2s32(event.MouseInput.X, event.MouseInput.Y); m_touch_hovered.reset(); } diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h index 071024120..2f770f9f5 100644 --- a/src/gui/modalMenu.h +++ b/src/gui/modalMenu.h @@ -26,11 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #endif -enum class PointerType { - Mouse, - Touch, -}; - struct PointerAction { v2s32 pos; u64 time; // ms @@ -74,14 +69,10 @@ public: porting::AndroidDialogState getAndroidUIInputState(); #endif - PointerType getPointerType() { return m_pointer_type; }; - protected: virtual std::wstring getLabelByID(s32 id) = 0; virtual std::string getNameByID(s32 id) = 0; - // Stores the last known pointer type. - PointerType m_pointer_type = PointerType::Mouse; // Stores the last known pointer position. // If the last input event was a mouse event, it's the cursor position. // If the last input event was a touch event, it's the finger position. @@ -102,9 +93,6 @@ protected: // This is set to true if the menu is currently processing a second-touch event. bool m_second_touch = false; - // This is set to true if the menu is currently processing a mouse event - // that was synthesized by the menu itself from a touch event. - bool m_simulated_mouse = false; private: IMenuManager *m_menumgr; diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index 4a673ccf3..f3301a64d 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -418,6 +418,11 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc) m_status_text->setVisible(false); } +TouchControls::~TouchControls() +{ + releaseAll(); +} + void TouchControls::addButton(std::vector &buttons, touch_gui_button_id id, const std::string &image, const recti &rect, bool visible) { @@ -843,6 +848,7 @@ void TouchControls::emitMouseEvent(EMOUSE_INPUT_EVENT type) event.MouseInput.Control = false; event.MouseInput.ButtonStates = 0; event.MouseInput.Event = type; + event.MouseInput.Simulated = true; m_receiver->OnEvent(event); } diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index 1787f6a5d..98ec806bd 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemdef.h" #include "client/game.h" +#include "util/basic_macros.h" namespace irr { @@ -136,6 +137,8 @@ class TouchControls { public: TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc); + ~TouchControls(); + DISABLE_CLASS_COPY(TouchControls); void translateEvent(const SEvent &event); void applyContextControls(const TouchInteractionMode &mode); From f5076723e83ef93f4d0a2ad2c5b590841d903e96 Mon Sep 17 00:00:00 2001 From: grorp Date: Sat, 28 Sep 2024 11:23:13 +0200 Subject: [PATCH 170/200] Android: Fix camera jump when switching to mouse mode Easy way to reproduce: 1. Connect a bluetooth mouse to your Android phone with Minetest installed 2. Play Minetest 3. Slowly move the mouse to the right so that the camera rotates continously 4. While still moving the mouse continously, tap the screen a few times per second Before this commit: The camera jumps around randomly. After this commit: The camera moves like it should. This is a combination of two Irrlicht changes copied from MoNTE48/irrlicht and one Minetest change authored by me. I have no idea why this works, but it does work and I have spent way too much time on this bug already. --- irr/src/CIrrDeviceSDL.cpp | 15 +++++++++++---- irr/src/CIrrDeviceSDL.h | 5 +++++ src/client/game.cpp | 4 +++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index 14d996e47..6d1b45886 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -721,12 +721,19 @@ bool CIrrDeviceSDL::run() irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED; - MouseX = irrevent.MouseInput.X = - static_cast(SDL_event.motion.x * ScaleX); - MouseY = irrevent.MouseInput.Y = - static_cast(SDL_event.motion.y * ScaleY); + MouseXRel = static_cast(SDL_event.motion.xrel * ScaleX); MouseYRel = static_cast(SDL_event.motion.yrel * ScaleY); + if (!SDL_GetRelativeMouseMode()) { + MouseX = static_cast(SDL_event.motion.x * ScaleX); + MouseY = static_cast(SDL_event.motion.y * ScaleY); + } else { + MouseX += MouseXRel; + MouseY += MouseYRel; + } + irrevent.MouseInput.X = MouseX; + irrevent.MouseInput.Y = MouseY; + irrevent.MouseInput.ButtonStates = MouseButtonStates; irrevent.MouseInput.Shift = (keymod & KMOD_SHIFT) != 0; irrevent.MouseInput.Control = (keymod & KMOD_CTRL) != 0; diff --git a/irr/src/CIrrDeviceSDL.h b/irr/src/CIrrDeviceSDL.h index f881bba5c..7156c19b6 100644 --- a/irr/src/CIrrDeviceSDL.h +++ b/irr/src/CIrrDeviceSDL.h @@ -158,9 +158,13 @@ public: //! Sets the new position of the cursor. void setPosition(s32 x, s32 y) override { +#ifndef __ANDROID__ + // On Android, this somehow results in a camera jump when enabling + // relative mouse mode and it isn't supported anyway. SDL_WarpMouseInWindow(Device->Window, static_cast(x / Device->ScaleX), static_cast(y / Device->ScaleY)); +#endif if (SDL_GetRelativeMouseMode()) { // There won't be an event for this warp (details on libsdl-org/SDL/issues/6034) @@ -298,6 +302,7 @@ private: #endif s32 MouseX, MouseY; + // these two only continue to exist for some Emscripten stuff idk about s32 MouseXRel, MouseYRel; u32 MouseButtonStates; diff --git a/src/client/game.cpp b/src/client/game.cpp index 090af05c9..32c57ec1b 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2679,7 +2679,7 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime) cur_control->setVisible(false); } - if (m_first_loop_after_window_activation) { + if (m_first_loop_after_window_activation && !g_touchcontrols) { m_first_loop_after_window_activation = false; input->setMousePos(driver->getScreenSize().Width / 2, @@ -2695,6 +2695,8 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime) m_first_loop_after_window_activation = true; } + if (g_touchcontrols) + m_first_loop_after_window_activation = true; } // Get the factor to multiply with sensitivity to get the same mouse/joystick From bd15f26c3500bbb3cf673199d505d8a2957250bf Mon Sep 17 00:00:00 2001 From: grorp Date: Sat, 28 Sep 2024 11:23:16 +0200 Subject: [PATCH 171/200] Disable automatic switching on Linux to avoid bug on X11 --- src/defaultsettings.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index f1756bd8e..2915caa48 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -97,7 +97,17 @@ void set_default_settings() // Client settings->setDefault("address", ""); settings->setDefault("enable_sound", "true"); +#if defined(__unix__) && !defined(__APPLE__) && !defined (__ANDROID__) + // On Linux+X11 (not Linux+Wayland or Linux+XWayland), I've encountered a bug + // where fake mouse events were generated from touch events if in relative + // mouse mode, resulting in the touchscreen controls being instantly disabled + // again and thus making them unusable. + // => We can't switch based on the last input method used. + // => Fall back to hardware detection. + settings->setDefault("touch_controls", bool_to_cstr(has_touch)); +#else settings->setDefault("touch_controls", "auto"); +#endif // Since GUI scaling shouldn't suddenly change during a session, we use // hardware detection for "touch_gui" instead of switching based on the last // input method used. From 3f5a58a4e5d0477aa296ed3449e9e7ca1c1a0f8a Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 9 Oct 2024 18:46:21 +0200 Subject: [PATCH 172/200] Fix rebase mistake in #14840 after #14749 Old enable_touch was used instead of new touch_gui. --- builtin/fstk/tabview.lua | 4 ++-- builtin/mainmenu/tab_local.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua index 9f8889143..42fc9ac18 100644 --- a/builtin/fstk/tabview.lua +++ b/builtin/fstk/tabview.lua @@ -66,13 +66,13 @@ local function get_formspec(self) local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize) - local ENABLE_TOUCH = core.settings:get_bool("enable_touch") + local TOUCH_GUI = core.settings:get_bool("touch_gui") local orig_tsize = tab.tabsize or { width = self.width, height = self.height } local tsize = { width = orig_tsize.width, height = orig_tsize.height } tsize.height = tsize.height + TABHEADER_H -- tabheader included in formspec size - + (ENABLE_TOUCH and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP) + + (TOUCH_GUI and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP) + GAMEBAR_H -- gamebar included in formspec size if self.parent == nil and not prepend then diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index f0a7255d7..8d807cc79 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -92,11 +92,11 @@ function singleplayer_refresh_gamebar() end end - local ENABLE_TOUCH = core.settings:get_bool("enable_touch") + local TOUCH_GUI = core.settings:get_bool("touch_gui") local gamebar_pos_y = MAIN_TAB_H + TABHEADER_H -- tabheader included in formspec size - + (ENABLE_TOUCH and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP) + + (TOUCH_GUI and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP) local btnbar = buttonbar_create( "game_button_bar", From c8f1efebeaca041959859f2547931bcd108a35fc Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 10 Oct 2024 17:40:06 +0200 Subject: [PATCH 173/200] Use execvp in fs::RecursiveDelete() --- src/filesys.cpp | 73 ++++++++++++++++++----------------- src/unittest/test_filesys.cpp | 31 +++++++++++++++ 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/filesys.cpp b/src/filesys.cpp index 196aca080..b0a1f318e 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #endif + #ifdef __linux__ #include #include @@ -43,6 +44,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif #endif +#ifdef _WIN32 +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + // Error from last OS call as string #ifdef _WIN32 #define LAST_OS_ERROR() porting::ConvertError(GetLastError()) @@ -59,11 +73,6 @@ namespace fs * Windows * ***********/ -#include -#include -#include -#include - std::vector GetDirListing(const std::string &pathstring) { std::vector listing; @@ -273,12 +282,6 @@ bool CopyFileContents(const std::string &source, const std::string &target) * POSIX * *********/ -#include -#include -#include -#include -#include - std::vector GetDirListing(const std::string &pathstring) { std::vector listing; @@ -381,41 +384,41 @@ bool RecursiveDelete(const std::string &path) Execute the 'rm' command directly, by fork() and execve() */ - infostream<<"Removing \""< argv = { + "rm", "-rf", path.c_str(), - NULL + nullptr }; - verbosestream<<"Executing '"<(argv.data())); - execv(argv[0], const_cast(argv)); - - // Execv shouldn't return. Failed. + // note: use cerr because our logging won't flush in forked process + std::cerr << "exec errno: " << errno << ": " << strerror(errno) + << std::endl; _exit(1); - } - else - { + } else { // Parent - int child_status; + int status; pid_t tpid; - do{ - tpid = wait(&child_status); - }while(tpid != child_pid); - return (child_status == 0); + do + tpid = waitpid(child_pid, &status, 0); + while (tpid != child_pid); + return WIFEXITED(status) && WEXITSTATUS(status) == 0; } } diff --git a/src/unittest/test_filesys.cpp b/src/unittest/test_filesys.cpp index fd25d2de9..e24e79374 100644 --- a/src/unittest/test_filesys.cpp +++ b/src/unittest/test_filesys.cpp @@ -42,6 +42,7 @@ public: void testSafeWriteToFile(); void testCopyFileContents(); void testNonExist(); + void testRecursiveDelete(); }; static TestFileSys g_test_instance; @@ -56,6 +57,7 @@ void TestFileSys::runTests(IGameDef *gamedef) TEST(testSafeWriteToFile); TEST(testCopyFileContents); TEST(testNonExist); + TEST(testRecursiveDelete); } //////////////////////////////////////////////////////////////////////////////// @@ -338,3 +340,32 @@ void TestFileSys::testNonExist() auto ifs = open_ifstream(path.c_str(), false); UASSERT(!ifs.good()); } + +void TestFileSys::testRecursiveDelete() +{ + std::string dirs[2]; + dirs[0] = getTestTempDirectory() + DIR_DELIM "a"; + dirs[1] = dirs[0] + DIR_DELIM "b"; + + std::string files[2] = { + dirs[0] + DIR_DELIM "file1", + dirs[1] + DIR_DELIM "file2" + }; + + for (auto &it : dirs) + fs::CreateDir(it); + for (auto &it : files) + open_ofstream(it.c_str(), false).close(); + + for (auto &it : dirs) + UASSERT(fs::IsDir(it)); + for (auto &it : files) + UASSERT(fs::IsFile(it)); + + UASSERT(fs::RecursiveDelete(dirs[0])); + + for (auto &it : dirs) + UASSERT(!fs::IsDir(it)); + for (auto &it : files) + UASSERT(!fs::IsFile(it)); +} From 7e4919c6edd79d550ef0523755c7d07d1f63c94d Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Tue, 14 May 2024 23:48:36 +0200 Subject: [PATCH 174/200] Refactor matrix4.h Sets the surprising row-major conventions used here straight. Renames rotateVect to rotateAndScaleVect: If the matrix also scales, that is applied as well by the method. Obsolete rotateVect variants are removed. The inverseRotateVect method is also renamed accordingly. Note that this applies the transpose of the product of the scale and rotation matrices, which inverts just the rotation. --- irr/include/matrix4.h | 64 ++++++++++----------------- irr/src/CB3DMeshFileLoader.cpp | 2 +- irr/src/CGLTFMeshFileLoader.cpp | 3 +- irr/src/CSkinnedMesh.cpp | 2 +- src/client/camera.cpp | 7 +-- src/client/clientmap.cpp | 3 +- src/client/hud.cpp | 4 +- src/client/particles.cpp | 10 +++-- src/client/shadows/dynamicshadows.cpp | 4 +- src/client/sky.cpp | 12 ++--- src/gui/guiScene.cpp | 3 +- 11 files changed, 46 insertions(+), 68 deletions(-) diff --git a/irr/include/matrix4.h b/irr/include/matrix4.h index 374fc6e4a..8fce0157a 100644 --- a/irr/include/matrix4.h +++ b/irr/include/matrix4.h @@ -24,7 +24,12 @@ namespace core { //! 4x4 matrix. Mostly used as transformation matrix for 3d calculations. -/** The matrix is a D3D style matrix, row major with translations in the 4th row. */ +/** Conventions: Matrices are considered to be in row-major order. + * Multiplication of a matrix A with a row vector v is the premultiplication vA. + * Translations are thus in the 4th row. + * The matrix product AB yields a matrix C such that vC = (vB)A: + * B is applied first, then A. + */ template class CMatrix4 { @@ -242,17 +247,11 @@ public: //! Translate a vector by the inverse of the translation part of this matrix. void inverseTranslateVect(vector3df &vect) const; - //! Rotate a vector by the inverse of the rotation part of this matrix. - void inverseRotateVect(vector3df &vect) const; + //! Scale a vector, then rotate by the inverse of the rotation part of this matrix. + [[nodiscard]] vector3d scaleThenInvRotVect(const vector3d &vect) const; - //! Rotate a vector by the rotation part of this matrix. - void rotateVect(vector3df &vect) const; - - //! An alternate transform vector method, writing into a second vector - void rotateVect(core::vector3df &out, const core::vector3df &in) const; - - //! An alternate transform vector method, writing into an array of 3 floats - void rotateVect(T *out, const core::vector3df &in) const; + //! Rotate and scale a vector. Applies both rotation & scale part of the matrix. + [[nodiscard]] vector3d rotateAndScaleVect(const vector3d &vect) const; //! Transforms the vector by this matrix /** This operation is performed as if the vector was 4d with the 4th component =1 */ @@ -1154,39 +1153,23 @@ inline bool CMatrix4::isIdentity_integer_base() const } template -inline void CMatrix4::rotateVect(vector3df &vect) const +inline vector3d CMatrix4::rotateAndScaleVect(const vector3d &v) const { - vector3d tmp(static_cast(vect.X), static_cast(vect.Y), static_cast(vect.Z)); - vect.X = static_cast(tmp.X * M[0] + tmp.Y * M[4] + tmp.Z * M[8]); - vect.Y = static_cast(tmp.X * M[1] + tmp.Y * M[5] + tmp.Z * M[9]); - vect.Z = static_cast(tmp.X * M[2] + tmp.Y * M[6] + tmp.Z * M[10]); -} - -//! An alternate transform vector method, writing into a second vector -template -inline void CMatrix4::rotateVect(core::vector3df &out, const core::vector3df &in) const -{ - out.X = in.X * M[0] + in.Y * M[4] + in.Z * M[8]; - out.Y = in.X * M[1] + in.Y * M[5] + in.Z * M[9]; - out.Z = in.X * M[2] + in.Y * M[6] + in.Z * M[10]; -} - -//! An alternate transform vector method, writing into an array of 3 floats -template -inline void CMatrix4::rotateVect(T *out, const core::vector3df &in) const -{ - out[0] = in.X * M[0] + in.Y * M[4] + in.Z * M[8]; - out[1] = in.X * M[1] + in.Y * M[5] + in.Z * M[9]; - out[2] = in.X * M[2] + in.Y * M[6] + in.Z * M[10]; + return { + v.X * M[0] + v.Y * M[4] + v.Z * M[8], + v.X * M[1] + v.Y * M[5] + v.Z * M[9], + v.X * M[2] + v.Y * M[6] + v.Z * M[10] + }; } template -inline void CMatrix4::inverseRotateVect(vector3df &vect) const +inline vector3d CMatrix4::scaleThenInvRotVect(const vector3d &v) const { - vector3d tmp(static_cast(vect.X), static_cast(vect.Y), static_cast(vect.Z)); - vect.X = static_cast(tmp.X * M[0] + tmp.Y * M[1] + tmp.Z * M[2]); - vect.Y = static_cast(tmp.X * M[4] + tmp.Y * M[5] + tmp.Z * M[6]); - vect.Z = static_cast(tmp.X * M[8] + tmp.Y * M[9] + tmp.Z * M[10]); + return { + v.X * M[0] + v.Y * M[1] + v.Z * M[2], + v.X * M[4] + v.Y * M[5] + v.Z * M[6], + v.X * M[8] + v.Y * M[9] + v.Z * M[10] + }; } template @@ -1247,8 +1230,7 @@ inline void CMatrix4::transformPlane(core::plane3d &plane) const // Transform the normal by the transposed inverse of the matrix CMatrix4 transposedInverse(*this, EM4CONST_INVERSE_TRANSPOSED); - vector3df normal = plane.Normal; - transposedInverse.rotateVect(normal); + vector3df normal = transposedInverse.rotateAndScaleVect(plane.Normal); plane.setPlane(member, normal.normalize()); } diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index 4d78860b2..60eeb5743 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -389,7 +389,7 @@ bool CB3DMeshFileLoader::readChunkVRTS(CSkinnedMesh::SJoint *inJoint) // Transform the Vertex position by nested node... inJoint->GlobalMatrix.transformVect(Vertex.Pos); - inJoint->GlobalMatrix.rotateVect(Vertex.Normal); + Vertex.Normal = inJoint->GlobalMatrix.rotateAndScaleVect(Vertex.Normal); // Add it... BaseVertices.push_back(Vertex); diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 64bbc10f1..b0c424936 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -354,8 +354,7 @@ static void transformVertices(std::vector &vertices, const cor // Apply scaling, rotation and rotation (in that order) to the position. transform.transformVect(vertex.Pos); // For the normal, we do not want to apply the translation. - // TODO note that this also applies scaling; the Irrlicht method is misnamed. - transform.rotateVect(vertex.Normal); + vertex.Normal = transform.rotateAndScaleVect(vertex.Normal); // Renormalize (length might have been affected by scaling). vertex.Normal.normalize(); } diff --git a/irr/src/CSkinnedMesh.cpp b/irr/src/CSkinnedMesh.cpp index 5db027abc..a0d3c3ec6 100644 --- a/irr/src/CSkinnedMesh.cpp +++ b/irr/src/CSkinnedMesh.cpp @@ -511,7 +511,7 @@ void CSkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) jointVertexPull.transformVect(thisVertexMove, weight.StaticPos); if (AnimateNormals) - jointVertexPull.rotateVect(thisNormalMove, weight.StaticNormal); + thisNormalMove = jointVertexPull.rotateAndScaleVect(weight.StaticNormal); if (!(*(weight.Moved))) { *(weight.Moved) = true; diff --git a/src/client/camera.cpp b/src/client/camera.cpp index bf9ec0bd5..615d30c87 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -405,10 +405,11 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) // Compute absolute camera position and target m_headnode->getAbsoluteTransformation().transformVect(m_camera_position, rel_cam_pos); - m_headnode->getAbsoluteTransformation().rotateVect(m_camera_direction, rel_cam_target - rel_cam_pos); + m_camera_direction = m_headnode->getAbsoluteTransformation() + .rotateAndScaleVect(rel_cam_target - rel_cam_pos); - v3f abs_cam_up; - m_headnode->getAbsoluteTransformation().rotateVect(abs_cam_up, rel_cam_up); + v3f abs_cam_up = m_headnode->getAbsoluteTransformation() + .rotateAndScaleVect(rel_cam_up); // Separate camera position for calculation v3f my_cp = m_camera_position; diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index d608ae2f6..ab826c775 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -1015,8 +1015,7 @@ int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor, v3f z_dir = z_directions[i]; core::CMatrix4 a; a.buildRotateFromTo(v3f(0,1,0), z_dir); - v3f dir = m_camera_direction; - a.rotateVect(dir); + v3f dir = a.rotateAndScaleVect(m_camera_direction); int br = 0; float step = BS*1.5; if(max_d > 35*BS) diff --git a/src/client/hud.cpp b/src/client/hud.cpp index e4c06b542..2a1acb288 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -536,9 +536,9 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) return; // Avoid zero divides // Angle according to camera view - v3f fore(0.f, 0.f, 1.f); scene::ICameraSceneNode *cam = client->getSceneManager()->getActiveCamera(); - cam->getAbsoluteTransformation().rotateVect(fore); + v3f fore = cam->getAbsoluteTransformation() + .rotateAndScaleVect(v3f(0.f, 0.f, 1.f)); int angle = - fore.getHorizontalAngle().Y; // Limit angle and ajust with given offset diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 1eab93579..3a2dace12 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -357,16 +357,18 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius, if (attached_absolute_pos_rot_matrix) { // Apply attachment rotation - attached_absolute_pos_rot_matrix->rotateVect(pp.vel); - attached_absolute_pos_rot_matrix->rotateVect(pp.acc); + pp.vel = attached_absolute_pos_rot_matrix->rotateAndScaleVect(pp.vel); + pp.acc = attached_absolute_pos_rot_matrix->rotateAndScaleVect(pp.acc); } if (attractor_obj) attractor_origin += attractor_obj->getPosition() / BS; if (attractor_direction_obj) { auto *attractor_absolute_pos_rot_matrix = attractor_direction_obj->getAbsolutePosRotMatrix(); - if (attractor_absolute_pos_rot_matrix) - attractor_absolute_pos_rot_matrix->rotateVect(attractor_direction); + if (attractor_absolute_pos_rot_matrix) { + attractor_direction = attractor_absolute_pos_rot_matrix + ->rotateAndScaleVect(attractor_direction); + } } pp.expirationtime = r_exp.pickWithin(); diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp index ffe7d4de5..2722c871b 100644 --- a/src/client/shadows/dynamicshadows.cpp +++ b/src/client/shadows/dynamicshadows.cpp @@ -137,8 +137,8 @@ void DirectionalLight::update_frustum(const Camera *cam, Client *client, bool fo // when camera offset changes, adjust the current frustum view matrix to avoid flicker v3s16 cam_offset = cam->getOffset(); if (cam_offset != shadow_frustum.camera_offset) { - v3f rotated_offset; - shadow_frustum.ViewMat.rotateVect(rotated_offset, intToFloat(cam_offset - shadow_frustum.camera_offset, BS)); + v3f rotated_offset = shadow_frustum.ViewMat.rotateAndScaleVect( + intToFloat(cam_offset - shadow_frustum.camera_offset, BS)); shadow_frustum.ViewMat.setTranslation(shadow_frustum.ViewMat.getTranslation() + rotated_offset); shadow_frustum.player += intToFloat(shadow_frustum.camera_offset - cam->getOffset(), BS); shadow_frustum.camera_offset = cam_offset; diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 65577418e..27640bc28 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -838,14 +838,10 @@ void Sky::updateStars() ); core::CMatrix4 a; a.buildRotateFromTo(v3f(0, 1, 0), r); - v3f p = v3f(-d, 1, -d); - v3f p1 = v3f(d, 1, -d); - v3f p2 = v3f(d, 1, d); - v3f p3 = v3f(-d, 1, d); - a.rotateVect(p); - a.rotateVect(p1); - a.rotateVect(p2); - a.rotateVect(p3); + v3f p = a.rotateAndScaleVect(v3f(-d, 1, -d)); + v3f p1 = a.rotateAndScaleVect(v3f(d, 1, -d)); + v3f p2 = a.rotateAndScaleVect(v3f(d, 1, d)); + v3f p3 = a.rotateAndScaleVect(v3f(-d, 1, d)); vertices.push_back(video::S3DVertex(p, {}, {}, {})); vertices.push_back(video::S3DVertex(p1, {}, {}, {})); vertices.push_back(video::S3DVertex(p2, {}, {}, {})); diff --git a/src/gui/guiScene.cpp b/src/gui/guiScene.cpp index 9293ebe22..33310fe35 100644 --- a/src/gui/guiScene.cpp +++ b/src/gui/guiScene.cpp @@ -225,8 +225,7 @@ void GUIScene::setCameraRotation(v3f rot) core::matrix4 mat; mat.setRotationDegrees(rot); - m_cam_pos = v3f(0.f, 0.f, m_cam_distance); - mat.rotateVect(m_cam_pos); + m_cam_pos = mat.rotateAndScaleVect(v3f(0.f, 0.f, m_cam_distance)); m_cam_pos += m_target_pos; m_cam->setPosition(m_cam_pos); From 521e678d39ceb26f7b7aaae41162cd24be45b54d Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Thu, 5 Sep 2024 17:16:55 +0200 Subject: [PATCH 175/200] Add binary glTF (.glb) support --- doc/lua_api.md | 5 +- games/devtest/mods/gltf/init.lua | 8 + .../mods/gltf/models/gltf_blender_cube.glb | Bin 0 -> 1752 bytes irr/src/CGLTFMeshFileLoader.cpp | 21 +- lib/tiniergltf/tiniergltf.hpp | 183 +++++++++++++++--- src/client/client.cpp | 2 +- src/server.cpp | 2 +- src/unittest/test_irr_gltf_mesh_loader.cpp | 5 +- 8 files changed, 184 insertions(+), 42 deletions(-) create mode 100644 games/devtest/mods/gltf/models/gltf_blender_cube.glb diff --git a/doc/lua_api.md b/doc/lua_api.md index ed41d7a22..6cb578810 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -274,7 +274,7 @@ Accepted formats are: images: .png, .jpg, .tga, (deprecated:) .bmp sounds: .ogg vorbis - models: .x, .b3d, .obj, .gltf (Minetest 5.10 or newer) + models: .x, .b3d, .obj, (since version 5.10:) .gltf, .glb Other formats won't be sent to the client (e.g. you can store .blend files in a folder for convenience, without the risk that such files are transferred) @@ -302,6 +302,9 @@ The glTF model file format for now only serves as a more modern alternative to the other static model file formats; it unlocks no special rendering features. +Binary glTF (`.glb`) files are supported and recommended over `.gltf` files +due to their space savings. + This means that many glTF features are not supported *yet*, including: * Animation diff --git a/games/devtest/mods/gltf/init.lua b/games/devtest/mods/gltf/init.lua index b5c2032bc..294da9145 100644 --- a/games/devtest/mods/gltf/init.lua +++ b/games/devtest/mods/gltf/init.lua @@ -18,6 +18,14 @@ do register_entity("blender_cube", cube_textures) register_entity("blender_cube_scaled", cube_textures) register_entity("blender_cube_matrix_transform", cube_textures) + minetest.register_entity("gltf:blender_cube_glb", { + initial_properties = { + visual = "mesh", + mesh = "gltf_blender_cube.glb", + textures = cube_textures, + backface_culling = true, + }, + }) end register_entity("snow_man", {"gltf_snow_man.png"}) register_entity("spider", {"gltf_spider.png"}) diff --git a/games/devtest/mods/gltf/models/gltf_blender_cube.glb b/games/devtest/mods/gltf/models/gltf_blender_cube.glb new file mode 100644 index 0000000000000000000000000000000000000000..b1894fc4f276ed133ad27096109b05165001915c GIT binary patch literal 1752 zcmb7DZEq4m5FY!c)%sGk`t`D(Pdtv-@(ljBBJz$}iz#X|e(1wus&+upX zbDUXs?RrwE3%B$3JTp6UdxK%?-39>s+yRI;0X{UH`i!|z#A8;Pu>tpa=*B_FO6=oB z82CYC43J_R4Y}Xrp;3M57}IRZPUQ;BWK$kSUf?6xPFZYjvZ#v*Sjm!F#7gZM^W72p zSX0DI<_A4a0qbmjc4f`jh({NKD)E&`hhvSX>kg6LPFtq<3l?ETl0VrAowBSfGRC1d z@?vi)0d5?JUS|@MIb)xlX0=sy>Y`HJX?!ZzSSf9?*70e@aT;&GSgd4YC!XK)xhNVjoh$yAwe$Wu@9Y$H%1LX4nXU=n6>ai8SnAiJF3?4OZ>i&&+9ch#TDi{eWB&J zPFFc7zD!p>lB;wTUnLjvJSq>_NnP0_m+1=}lD24G`cfR-M>_fcl1bhYJDFSXB~Inj zeWCzv;taB$@(G;uDXx+VldLE1!m>PxGw6AKypp|=#D{EJ2Ie6gNm}P*%lI4e5e_Bc z$u{uOM9fZOmG_Hf^WTks6-!8Y#O@CbeZCD@ea literal 0 HcmV?d00001 diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index b0c424936..aee98b2e6 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -14,8 +14,6 @@ #include "vector3d.h" #include "os.h" -#include "tiniergltf.hpp" - #include #include #include @@ -303,13 +301,11 @@ std::array SelfType::getNormalizedValues( return values; } -/** - * The most basic portion of the code base. This tells irllicht if this file has a .gltf extension. -*/ bool SelfType::isALoadableFileExtension( const io::path& filename) const { - return core::hasFileExtension(filename, "gltf"); + return core::hasFileExtension(filename, "gltf") || + core::hasFileExtension(filename, "glb"); } /** @@ -662,6 +658,7 @@ void SelfType::MeshExtractor::copyTCoords( */ std::optional SelfType::tryParseGLTF(io::IReadFile* file) { + const bool isGlb = core::hasFileExtension(file->getFileName(), "glb"); auto size = file->getSize(); if (size < 0) // this can happen if `ftell` fails return std::nullopt; @@ -670,15 +667,11 @@ std::optional SelfType::tryParseGLTF(io::IReadFile* file) return std::nullopt; // We probably don't need this, but add it just to be sure. buf[size] = '\0'; - Json::CharReaderBuilder builder; - const std::unique_ptr reader(builder.newCharReader()); - Json::Value json; - JSONCPP_STRING err; - if (!reader->parse(buf.get(), buf.get() + size, &json, &err)) { - return std::nullopt; - } try { - return tiniergltf::GlTF(json); + if (isGlb) + return tiniergltf::readGlb(buf.get(), size); + else + return tiniergltf::readGlTF(buf.get(), size); } catch (const std::runtime_error &e) { os::Printer::log("glTF loader", e.what(), ELL_ERROR); return std::nullopt; diff --git a/lib/tiniergltf/tiniergltf.hpp b/lib/tiniergltf/tiniergltf.hpp index 6a861556e..d4db7cc64 100644 --- a/lib/tiniergltf/tiniergltf.hpp +++ b/lib/tiniergltf/tiniergltf.hpp @@ -1,6 +1,9 @@ #pragma once #include +#include "util/base64.h" + +#include #include #include #include @@ -13,7 +16,6 @@ #include #include #include -#include "util/base64.h" namespace tiniergltf { @@ -460,7 +462,8 @@ struct Buffer { std::optional name; std::string data; Buffer(const Json::Value &o, - const std::function &resolveURI) + const std::function &resolveURI, + std::optional &&glbData = std::nullopt) : byteLength(as(o["byteLength"])) { check(o.isObject()); @@ -468,24 +471,32 @@ struct Buffer { if (o.isMember("name")) { name = as(o["name"]); } - check(o.isMember("uri")); - bool dataURI = false; - const std::string uri = as(o["uri"]); - for (auto &prefix : std::array { - "data:application/octet-stream;base64,", - "data:application/gltf-buffer;base64," - }) { - if (std::string_view(uri).substr(0, prefix.length()) == prefix) { - auto view = std::string_view(uri).substr(prefix.length()); - check(base64_is_valid(view)); - data = base64_decode(view); - dataURI = true; - break; + if (glbData.has_value()) { + check(!o.isMember("uri")); + data = *std::move(glbData); + // GLB allows padding, which need not be reflected in the JSON + check(byteLength + 3 >= data.size()); + check(data.size() >= byteLength); + } else { + check(o.isMember("uri")); + bool dataURI = false; + const std::string uri = as(o["uri"]); + for (auto &prefix : std::array { + "data:application/octet-stream;base64,", + "data:application/gltf-buffer;base64," + }) { + if (std::string_view(uri).substr(0, prefix.length()) == prefix) { + auto view = std::string_view(uri).substr(prefix.length()); + check(base64_is_valid(view)); + data = base64_decode(view); + dataURI = true; + break; + } } + if (!dataURI) + data = resolveURI(uri); + check(data.size() >= byteLength); } - if (!dataURI) - data = resolveURI(uri); - check(data.size() >= byteLength); data.resize(byteLength); } }; @@ -1093,6 +1104,12 @@ struct Texture { }; template<> Texture as(const Json::Value &o) { return o; } +using UriResolver = std::function; +static inline std::string uriError(const std::string &uri) { + // only base64 data URI support by default + throw std::runtime_error("unsupported URI: " + uri); +} + struct GlTF { std::optional> accessors; std::optional> animations; @@ -1111,12 +1128,10 @@ struct GlTF { std::optional> scenes; std::optional> skins; std::optional> textures; - static std::string uriError(const std::string &uri) { - // only base64 data URI support by default - throw std::runtime_error("unsupported URI: " + uri); - } + GlTF(const Json::Value &o, - const std::function &resolveURI = uriError) + const UriResolver &resolveUri = uriError, + std::optional &&glbData = std::nullopt) : asset(as(o["asset"])) { check(o.isObject()); @@ -1138,7 +1153,8 @@ struct GlTF { std::vector bufs; bufs.reserve(b.size()); for (Json::ArrayIndex i = 0; i < b.size(); ++i) { - bufs.emplace_back(b[i], resolveURI); + bufs.emplace_back(b[i], resolveUri, + i == 0 ? std::move(glbData) : std::nullopt); } check(bufs.size() >= 1); buffers = std::move(bufs); @@ -1354,4 +1370,123 @@ struct GlTF { } }; +// std::span is C++ 20, so we roll our own little struct here. +template +struct Span { + T *ptr; + uint32_t len; + bool empty() const { + return len == 0; + } + T *end() const { + return ptr + len; + } + template + Span cast() const { + return {(U *) ptr, len}; + } +}; + +static Json::Value readJson(Span span) { + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + Json::Value json; + JSONCPP_STRING err; + if (!reader->parse(span.ptr, span.end(), &json, &err)) + throw std::runtime_error(std::string("invalid JSON: ") + err); + return json; +} + +inline GlTF readGlb(const char *data, std::size_t len, const UriResolver &resolveUri = uriError) { + struct Chunk { + uint32_t type; + Span span; + }; + + struct Stream { + Span span; + + bool eof() const { + return span.empty(); + } + + void advance(uint32_t n) { + span.len -= n; + span.ptr += n; + } + + uint32_t readUint32() { + if (span.len < 4) + throw std::runtime_error("premature EOF"); + uint32_t res = 0; + for (int i = 0; i < 4; ++i) + res += span.ptr[i] << (i * 8); + advance(4); + return res; + } + + Chunk readChunk() { + const auto chunkLen = readUint32(); + if (chunkLen % 4 != 0) + throw std::runtime_error("chunk length must be multiple of 4"); + const auto chunkType = readUint32(); + + auto chunkPtr = span.ptr; + if (span.len < chunkLen) + throw std::runtime_error("premature EOF"); + advance(chunkLen); + return {chunkType, {chunkPtr, chunkLen}}; + } + }; + + constexpr uint32_t MAGIC_GLTF = 0x46546C67; + constexpr uint32_t MAGIC_JSON = 0x4E4F534A; + constexpr uint32_t MAGIC_BIN = 0x004E4942; + + if (len > std::numeric_limits::max()) + throw std::runtime_error("too large"); + + Stream is{{(const uint8_t *) data, static_cast(len)}}; + + const auto magic = is.readUint32(); + if (magic != MAGIC_GLTF) + throw std::runtime_error("wrong magic number"); + const auto version = is.readUint32(); + if (version != 2) + throw std::runtime_error("wrong version"); + const auto length = is.readUint32(); + if (length != len) + throw std::runtime_error("wrong length"); + + const auto json = is.readChunk(); + if (json.type != MAGIC_JSON) + throw std::runtime_error("expected JSON chunk"); + + std::optional buffer; + if (!is.eof()) { + const auto chunk = is.readChunk(); + if (chunk.type == MAGIC_BIN) + buffer = std::string((const char *) chunk.span.ptr, chunk.span.len); + else if (chunk.type == MAGIC_JSON) + throw std::runtime_error("unexpected chunk"); + // Ignore all other chunks. We still want to validate that + // 1. These chunks are valid; + // 2. These chunks are *not* JSON or BIN chunks + while (!is.eof()) { + const auto type = is.readChunk().type; + if (type == MAGIC_JSON || type == MAGIC_BIN) + throw std::runtime_error("unexpected chunk"); + } + } + + return GlTF(readJson(json.span.cast()), resolveUri, std::move(buffer)); +} + +inline GlTF readGlTF(const char *data, std::size_t len, const UriResolver &resolveUri = uriError) { + if (len > std::numeric_limits::max()) + throw std::runtime_error("too large"); + + return GlTF(readJson({data, static_cast(len)}), resolveUri); +} + } diff --git a/src/client/client.cpp b/src/client/client.cpp index 2cf22b328..0f90bca97 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -827,7 +827,7 @@ bool Client::loadMedia(const std::string &data, const std::string &filename, } const char *model_ext[] = { - ".x", ".b3d", ".obj", ".gltf", + ".x", ".b3d", ".obj", ".gltf", ".glb", NULL }; name = removeStringEnd(filename, model_ext); diff --git a/src/server.cpp b/src/server.cpp index 037857b21..7634e2433 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2519,7 +2519,7 @@ bool Server::addMediaFile(const std::string &filename, const char *supported_ext[] = { ".png", ".jpg", ".bmp", ".tga", ".ogg", - ".x", ".b3d", ".obj", ".gltf", + ".x", ".b3d", ".obj", ".gltf", ".glb", // Custom translation file format ".tr", NULL diff --git a/src/unittest/test_irr_gltf_mesh_loader.cpp b/src/unittest/test_irr_gltf_mesh_loader.cpp index 847360be3..99cb4b1b5 100644 --- a/src/unittest/test_irr_gltf_mesh_loader.cpp +++ b/src/unittest/test_irr_gltf_mesh_loader.cpp @@ -87,7 +87,10 @@ SECTION("minimal triangle") { } SECTION("blender cube") { - const auto mesh = loadMesh(model_stem + "blender_cube.gltf"); + const auto path = GENERATE( + model_stem + "blender_cube.gltf", + model_stem + "blender_cube.glb"); + const auto mesh = loadMesh(path); REQUIRE(mesh); REQUIRE(mesh->getMeshBufferCount() == 1); SECTION("vertex coordinates are correct") { From 2fee37f31b3bf0d8d47f16ecd68534b4691b7868 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Tue, 8 Oct 2024 16:43:45 +0200 Subject: [PATCH 176/200] Fix gltf / glb loader oversights - Avoid an unnecessary copy - Reject models requiring extensions Co-authored-by: DS --- irr/src/CGLTFMeshFileLoader.cpp | 5 +++++ irr/src/CGLTFMeshFileLoader.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index aee98b2e6..8ac96d4f4 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -320,6 +320,11 @@ IAnimatedMesh* SelfType::createMesh(io::IReadFile* file) if (!model.has_value()) { return nullptr; } + if (model->extensionsRequired) { + os::Printer::log("glTF loader", + "model requires extensions, but we support none", ELL_ERROR); + return nullptr; + } if (!(model->buffers.has_value() && model->bufferViews.has_value() diff --git a/irr/src/CGLTFMeshFileLoader.h b/irr/src/CGLTFMeshFileLoader.h index 39c3ea6dd..da306769e 100644 --- a/irr/src/CGLTFMeshFileLoader.h +++ b/irr/src/CGLTFMeshFileLoader.h @@ -98,7 +98,7 @@ private: public: MeshExtractor(tiniergltf::GlTF &&model, CSkinnedMesh *mesh) noexcept - : m_gltf_model(model), m_irr_model(mesh) {}; + : m_gltf_model(std::move(model)), m_irr_model(mesh) {}; /* Gets indices for the given mesh/primitive. * From 224066c1d3cf100191b465ff8f232fd09c5a369a Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Tue, 8 Oct 2024 20:34:16 +0200 Subject: [PATCH 177/200] Implement glTF texture wrapping support --- irr/include/vector2d.h | 4 +++ irr/src/CGLTFMeshFileLoader.cpp | 43 +++++++++++++++++++++++++++++---- lib/tiniergltf/tiniergltf.hpp | 30 ++++++++--------------- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/irr/include/vector2d.h b/irr/include/vector2d.h index 4c41389f4..caf69e6be 100644 --- a/irr/include/vector2d.h +++ b/irr/include/vector2d.h @@ -8,6 +8,7 @@ #include "dimension2d.h" #include +#include namespace irr { @@ -34,6 +35,9 @@ public: constexpr vector2d(const dimension2d &other) : X(other.Width), Y(other.Height) {} + explicit constexpr vector2d(const std::array &arr) : + X(arr[0]), Y(arr[1]) {} + // operators vector2d operator-() const { return vector2d(-X, -Y); } diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 8ac96d4f4..ab04fae8e 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -3,6 +3,7 @@ #include "CGLTFMeshFileLoader.h" +#include "SMaterialLayer.h" #include "coreutil.h" #include "CSkinnedMesh.h" #include "ISkinnedMesh.h" @@ -11,6 +12,7 @@ #include "matrix4.h" #include "path.h" #include "quaternion.h" +#include "vector2d.h" #include "vector3d.h" #include "os.h" @@ -381,6 +383,20 @@ static std::vector generateIndices(const std::size_t nVerts) return indices; } +using Wrap = tiniergltf::Sampler::Wrap; +static video::E_TEXTURE_CLAMP convertTextureWrap(const Wrap wrap) { + switch (wrap) { + case Wrap::REPEAT: + return video::ETC_REPEAT; + case Wrap::CLAMP_TO_EDGE: + return video::ETC_CLAMP_TO_EDGE; + case Wrap::MIRRORED_REPEAT: + return video::ETC_MIRROR; + default: + throw std::runtime_error("invalid sampler wrapping mode"); + } +} + /** * Load up the rawest form of the model. The vertex positions and indices. * Documentation: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes @@ -415,6 +431,8 @@ void SelfType::MeshExtractor::loadMesh( m_irr_model->addMeshBuffer( new SSkinMeshBuffer(std::move(*vertices), std::move(indices))); + auto *meshbuf = m_irr_model->getMeshBuffer(m_irr_model->getMeshBufferCount() - 1); + auto &irr_mat = meshbuf->getMaterial(); if (primitive.material.has_value()) { const auto &material = m_gltf_model.materials->at(*primitive.material); @@ -423,6 +441,13 @@ void SelfType::MeshExtractor::loadMesh( if (texture.has_value()) { const auto meshbufNr = m_irr_model->getMeshBufferCount() - 1; m_irr_model->setTextureSlot(meshbufNr, static_cast(texture->index)); + const auto samplerIdx = m_gltf_model.textures->at(texture->index).sampler; + if (samplerIdx.has_value()) { + auto &sampler = m_gltf_model.samplers->at(*samplerIdx); + auto &layer = irr_mat.TextureLayers[0]; + layer.TextureWrapU = convertTextureWrap(sampler.wrapS); + layer.TextureWrapV = convertTextureWrap(sampler.wrapT); + } } } } @@ -650,11 +675,19 @@ void SelfType::MeshExtractor::copyTCoords( const std::size_t accessorIdx, std::vector& vertices) const { - const auto accessor = createNormalizedValuesAccessor<2>(m_gltf_model, accessorIdx); - const auto count = std::visit([](auto &&a) { return a.getCount(); }, accessor); - for (std::size_t i = 0; i < count; ++i) { - const auto vals = getNormalizedValues(accessor, i); - vertices[i].TCoords = core::vector2df(vals[0], vals[1]); + const auto componentType = m_gltf_model.accessors->at(accessorIdx).componentType; + if (componentType == tiniergltf::Accessor::ComponentType::FLOAT) { + // If floats are used, they need not be normalized: Wrapping may take effect. + const auto accessor = Accessor>::make(m_gltf_model, accessorIdx); + for (std::size_t i = 0; i < accessor.getCount(); ++i) { + vertices[i].TCoords = core::vector2d(accessor.get(i)); + } + } else { + const auto accessor = createNormalizedValuesAccessor<2>(m_gltf_model, accessorIdx); + const auto count = std::visit([](auto &&a) { return a.getCount(); }, accessor); + for (std::size_t i = 0; i < count; ++i) { + vertices[i].TCoords = core::vector2d(getNormalizedValues(accessor, i)); + } } } diff --git a/lib/tiniergltf/tiniergltf.hpp b/lib/tiniergltf/tiniergltf.hpp index d4db7cc64..35440f5dd 100644 --- a/lib/tiniergltf/tiniergltf.hpp +++ b/lib/tiniergltf/tiniergltf.hpp @@ -980,21 +980,16 @@ struct Sampler { }; std::optional minFilter; std::optional name; - enum class WrapS { + enum class Wrap { REPEAT, CLAMP_TO_EDGE, MIRRORED_REPEAT, }; - WrapS wrapS; - enum class WrapT { - REPEAT, - CLAMP_TO_EDGE, - MIRRORED_REPEAT, - }; - WrapT wrapT; + Wrap wrapS; + Wrap wrapT; Sampler(const Json::Value &o) - : wrapS(WrapS::REPEAT) - , wrapT(WrapT::REPEAT) + : wrapS(Wrap::REPEAT) + , wrapT(Wrap::REPEAT) { check(o.isObject()); if (o.isMember("magFilter")) { @@ -1020,21 +1015,16 @@ struct Sampler { if (o.isMember("name")) { name = as(o["name"]); } + static std::unordered_map map = { + {10497, Wrap::REPEAT}, + {33071, Wrap::CLAMP_TO_EDGE}, + {33648, Wrap::MIRRORED_REPEAT}, + }; if (o.isMember("wrapS")) { - static std::unordered_map map = { - {10497, WrapS::REPEAT}, - {33071, WrapS::CLAMP_TO_EDGE}, - {33648, WrapS::MIRRORED_REPEAT}, - }; const auto &v = o["wrapS"]; check(v.isUInt64()); wrapS = map.at(v.asUInt64()); } if (o.isMember("wrapT")) { - static std::unordered_map map = { - {10497, WrapT::REPEAT}, - {33071, WrapT::CLAMP_TO_EDGE}, - {33648, WrapT::MIRRORED_REPEAT}, - }; const auto &v = o["wrapT"]; check(v.isUInt64()); wrapT = map.at(v.asUInt64()); } From d8274af670712c0688340106563ec7b814503162 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Wed, 15 May 2024 01:00:07 +0200 Subject: [PATCH 178/200] Refactor global inversed matrix usage (+ minor fix) Thanks to GreenXenith and Josiah for spotting a bug here --- irr/include/ISkinnedMesh.h | 8 +++++--- irr/src/CB3DMeshFileLoader.cpp | 1 + irr/src/CSkinnedMesh.cpp | 13 ++++++++----- irr/src/CXMeshFileLoader.cpp | 4 ++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/irr/include/ISkinnedMesh.h b/irr/include/ISkinnedMesh.h index bb611bba2..869327bcd 100644 --- a/irr/include/ISkinnedMesh.h +++ b/irr/include/ISkinnedMesh.h @@ -159,15 +159,17 @@ public: core::array Weights; //! Unnecessary for loaders, will be overwritten on finalize - core::matrix4 GlobalMatrix; + core::matrix4 GlobalMatrix; // loaders may still choose to set this (temporarily) to calculate absolute vertex data. core::matrix4 GlobalAnimatedMatrix; core::matrix4 LocalAnimatedMatrix; + + //! These should be set by loaders. core::vector3df Animatedposition; core::vector3df Animatedscale; core::quaternion Animatedrotation; - core::matrix4 GlobalInversedMatrix; // the x format pre-calculates this - + // The .x and .gltf formats pre-calculate this + std::optional GlobalInversedMatrix; private: //! Internal members used by CSkinnedMesh friend class CSkinnedMesh; diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index 60eeb5743..cf6a980d8 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -390,6 +390,7 @@ bool CB3DMeshFileLoader::readChunkVRTS(CSkinnedMesh::SJoint *inJoint) // Transform the Vertex position by nested node... inJoint->GlobalMatrix.transformVect(Vertex.Pos); Vertex.Normal = inJoint->GlobalMatrix.rotateAndScaleVect(Vertex.Normal); + Vertex.Normal.normalize(); // renormalize: normal might have been skewed by scaling // Add it... BaseVertices.push_back(Vertex); diff --git a/irr/src/CSkinnedMesh.cpp b/irr/src/CSkinnedMesh.cpp index a0d3c3ec6..56ef3efe1 100644 --- a/irr/src/CSkinnedMesh.cpp +++ b/irr/src/CSkinnedMesh.cpp @@ -222,6 +222,7 @@ void CSkinnedMesh::buildAllLocalAnimatedMatrices() // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility. // Not tested so far if this was correct or wrong before quaternion fix! + // Note that using getMatrix_transposed inverts the rotation. joint->Animatedrotation.getMatrix_transposed(joint->LocalAnimatedMatrix); // --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() --- @@ -496,8 +497,8 @@ void CSkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) { if (joint->Weights.size()) { // Find this joints pull on vertices... - core::matrix4 jointVertexPull(core::matrix4::EM4CONST_NOTHING); - jointVertexPull.setbyproduct(joint->GlobalAnimatedMatrix, joint->GlobalInversedMatrix); + // Note: It is assumed that the global inversed matrix has been calculated at this point. + core::matrix4 jointVertexPull = joint->GlobalAnimatedMatrix * joint->GlobalInversedMatrix.value(); core::vector3df thisVertexMove, thisNormalMove; @@ -510,8 +511,10 @@ void CSkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) // Pull this vertex... jointVertexPull.transformVect(thisVertexMove, weight.StaticPos); - if (AnimateNormals) + if (AnimateNormals) { thisNormalMove = jointVertexPull.rotateAndScaleVect(weight.StaticNormal); + thisNormalMove.normalize(); // must renormalize after potentially scaling + } if (!(*(weight.Moved))) { *(weight.Moved) = true; @@ -764,9 +767,9 @@ void CSkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint) joint->LocalAnimatedMatrix = joint->LocalMatrix; joint->GlobalAnimatedMatrix = joint->GlobalMatrix; - if (joint->GlobalInversedMatrix.isIdentity()) { // might be pre calculated + if (!joint->GlobalInversedMatrix.has_value()) { // might be pre calculated joint->GlobalInversedMatrix = joint->GlobalMatrix; - joint->GlobalInversedMatrix.makeInverse(); // slow + joint->GlobalInversedMatrix->makeInverse(); // slow } for (u32 j = 0; j < joint->Children.size(); ++j) diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index fc0e6e237..967fc367c 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -990,9 +990,9 @@ bool CXMeshFileLoader::parseDataObjectSkinWeights(SXMesh &mesh) // transforms the mesh vertices to the space of the bone // When concatenated to the bone's transform, this provides the // world space coordinates of the mesh as affected by the bone - core::matrix4 &MatrixOffset = joint->GlobalInversedMatrix; - + core::matrix4 MatrixOffset; readMatrix(MatrixOffset); + joint->GlobalInversedMatrix = MatrixOffset; if (!checkForOneFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Skin Weights found in x file", ELL_WARNING); From 323fc0a7982f26448318ff502a34152e92a4f868 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Sat, 6 Jan 2024 20:21:04 +0100 Subject: [PATCH 179/200] Add glTF animation support --- .gitattributes | 2 + doc/lua_api.md | 7 +- games/devtest/mods/gltf/LICENSE.md | 2 +- games/devtest/mods/gltf/init.lua | 26 ++ .../mods/gltf/models/gltf_simple_skin.gltf | 1 + .../gltf/models/gltf_spider_animated.gltf | 1 + irr/src/CGLTFMeshFileLoader.cpp | 336 ++++++++++++++---- irr/src/CGLTFMeshFileLoader.h | 38 +- src/unittest/test_irr_gltf_mesh_loader.cpp | 87 ++++- 9 files changed, 421 insertions(+), 79 deletions(-) create mode 100644 games/devtest/mods/gltf/models/gltf_simple_skin.gltf create mode 100644 games/devtest/mods/gltf/models/gltf_spider_animated.gltf diff --git a/.gitattributes b/.gitattributes index 06b76c6c8..ecd9a7a29 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,5 @@ *.cpp diff=cpp *.h diff=cpp + +*.gltf binary diff --git a/doc/lua_api.md b/doc/lua_api.md index 6cb578810..0596c1e2f 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -294,7 +294,7 @@ depends on by supplying a file with an equal name. Only a subset of model file format features is supported: Simple textured meshes (with multiple textures), optionally with normals. -The .x and .b3d formats additionally support skeletal animation. +The .x, .b3d and .gltf formats additionally support (a single) animation. #### glTF @@ -307,7 +307,10 @@ due to their space savings. This means that many glTF features are not supported *yet*, including: -* Animation +* Animations + * Only a single animation is supported, + use frame ranges within this animation. + * Only integer frames are supported. * Cameras * Materials * Only base color textures are supported diff --git a/games/devtest/mods/gltf/LICENSE.md b/games/devtest/mods/gltf/LICENSE.md index b0ae5fef5..6c3828a4a 100644 --- a/games/devtest/mods/gltf/LICENSE.md +++ b/games/devtest/mods/gltf/LICENSE.md @@ -1,4 +1,4 @@ -glTF test model (and corresponding texture) licenses: +The glTF test models (and corresponding textures) in this mod are all licensed freely: * Spider (`gltf_spider.gltf`, `gltf_spider.png`): * By [archfan7411](https://github.com/archfan7411) diff --git a/games/devtest/mods/gltf/init.lua b/games/devtest/mods/gltf/init.lua index 294da9145..1a17ac05f 100644 --- a/games/devtest/mods/gltf/init.lua +++ b/games/devtest/mods/gltf/init.lua @@ -27,8 +27,34 @@ do }, }) end + register_entity("snow_man", {"gltf_snow_man.png"}) register_entity("spider", {"gltf_spider.png"}) + +minetest.register_entity("gltf:spider_animated", { + initial_properties = { + visual = "mesh", + mesh = "gltf_spider_animated.gltf", + textures = {"gltf_spider.png"}, + }, + on_activate = function(self) + self.object:set_animation({x = 0, y = 140}, 1) + end +}) + +minetest.register_entity("gltf:simple_skin", { + initial_properties = { + visual = "mesh", + visual_size = vector.new(5, 5, 5), + mesh = "gltf_simple_skin.gltf", + textures = {}, + backface_culling = false + }, + on_activate = function(self) + self.object:set_animation({x = 0, y = 5.5}, 1) + end +}) + -- Note: Model has an animation, but we can use it as a static test nevertheless -- The claws rendering incorrectly from one side is expected behavior: -- They use an unsupported double-sided material. diff --git a/games/devtest/mods/gltf/models/gltf_simple_skin.gltf b/games/devtest/mods/gltf/models/gltf_simple_skin.gltf new file mode 100644 index 000000000..3d6c24a6c --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_simple_skin.gltf @@ -0,0 +1 @@ +{"scene":0,"scenes":[{"nodes":[0,1]}],"nodes":[{"skin":0,"mesh":0},{"children":[2]},{"translation":[0.0,1.0,0.0],"rotation":[0.0,0.0,0.0,1.0]}],"meshes":[{"primitives":[{"attributes":{"POSITION":1,"JOINTS_0":2,"WEIGHTS_0":3},"indices":0}]}],"skins":[{"inverseBindMatrices":4,"joints":[1,2]}],"animations":[{"channels":[{"sampler":0,"target":{"node":2,"path":"rotation"}}],"samplers":[{"input":5,"interpolation":"LINEAR","output":6}]}],"buffers":[{"uri":"data:application/gltf-buffer;base64,AAABAAMAAAADAAIAAgADAAUAAgAFAAQABAAFAAcABAAHAAYABgAHAAkABgAJAAgAAAAAvwAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAvwAAAD8AAAAAAAAAPwAAAD8AAAAAAAAAvwAAgD8AAAAAAAAAPwAAgD8AAAAAAAAAvwAAwD8AAAAAAAAAPwAAwD8AAAAAAAAAvwAAAEAAAAAAAAAAPwAAAEAAAAAA","byteLength":168},{"uri":"data:application/gltf-buffer;base64,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAgD4AAEA/AAAAAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAA=","byteLength":320},{"uri":"data:application/gltf-buffer;base64,AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8=","byteLength":128},{"uri":"data:application/gltf-buffer;base64,AAAAAAAAAD8AAIA/AADAPwAAAEAAACBAAABAQAAAYEAAAIBAAACQQAAAoEAAALBAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAPT9ND/0/TQ/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAPT9NL/0/TQ/AAAAAAAAAAD0/TS/9P00PwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAAAAAAAAAIA/","byteLength":240}],"bufferViews":[{"buffer":0,"byteLength":48,"target":34963},{"buffer":0,"byteOffset":48,"byteLength":120,"target":34962},{"buffer":1,"byteLength":320,"byteStride":16},{"buffer":2,"byteLength":128},{"buffer":3,"byteLength":240}],"accessors":[{"bufferView":0,"componentType":5123,"count":24,"type":"SCALAR"},{"bufferView":1,"componentType":5126,"count":10,"type":"VEC3","max":[0.5,2.0,0.0],"min":[-0.5,0.0,0.0]},{"bufferView":2,"componentType":5123,"count":10,"type":"VEC4"},{"bufferView":2,"byteOffset":160,"componentType":5126,"count":10,"type":"VEC4"},{"bufferView":3,"componentType":5126,"count":2,"type":"MAT4"},{"bufferView":4,"componentType":5126,"count":12,"type":"SCALAR","max":[5.5],"min":[0.0]},{"bufferView":4,"byteOffset":48,"componentType":5126,"count":12,"type":"VEC4","max":[0.0,0.0,0.707,1.0],"min":[0.0,0.0,-0.707,0.707]}],"asset":{"version":"2.0"}} diff --git a/games/devtest/mods/gltf/models/gltf_spider_animated.gltf b/games/devtest/mods/gltf/models/gltf_spider_animated.gltf new file mode 100644 index 000000000..79221b0c7 --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_spider_animated.gltf @@ -0,0 +1 @@ +{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[58]}],"nodes":[{"name":"Pincer.L","rotation":[0.03853772580623627,0.09671717882156372,0.5138389468193054,0.8515457510948181],"translation":[-2.2351741790771484e-08,0.2836739718914032,-2.2351741790771484e-08]},{"children":[0],"name":"JawBase.L","rotation":[-0.23922589421272278,-9.208349638356594e-08,-0.38811206817626953,0.8900224566459656],"scale":[1,1,0.9999999403953552],"translation":[8.097286041675034e-08,0.7702280879020691,-1.169656727029178e-07]},{"name":"Pincer.R","rotation":[0.038537755608558655,-0.09671713411808014,-0.5138388872146606,0.8515458106994629],"scale":[0.9999997615814209,0.9999999403953552,1],"translation":[2.9802322387695312e-08,0.2836737036705017,-2.9802322387695312e-08]},{"children":[2],"name":"JawBase.R","rotation":[-0.2392251342535019,1.9714243535418063e-06,0.3881126046180725,0.8900225758552551],"translation":[1.3833086898173974e-09,0.7702280282974243,-6.620245329713725e-08]},{"children":[1,3],"name":"Head","rotation":[0.4052415192127228,-3.4197712478652165e-13,-8.695541282577324e-07,0.9142096638679504],"translation":[-2.0781445141115906e-16,0.6883190274238586,-1.4901161193847656e-08]},{"children":[4],"name":"NeckBase","rotation":[-0.778048574924469,7.488795716881214e-08,1.7622618315726868e-06,0.6282041072845459],"translation":[-3.399441372928941e-14,0.3915250301361084,-2.3283064365386963e-09]},{"name":"Body.002","rotation":[0.17414046823978424,0,-4.151832513343834e-07,0.9847208261489868],"translation":[5.897654597759178e-14,1.0079095363616943,-1.2134763416327132e-08]},{"children":[6],"name":"Body.001","rotation":[0.6673352122306824,-7.632472941426077e-14,-1.5910509318928234e-06,0.7447575330734253],"scale":[1,0.9999999403953552,0.9999999403953552],"translation":[-3.962037268363458e-15,0.3915250301361084,-3.1428597502269895e-09]},{"name":"Leg4Fore.L","rotation":[-0.021953541785478592,0.030033688992261887,-0.4378480017185211,0.8982790112495422],"scale":[1,0.9999998211860657,1.0000001192092896],"translation":[1.9202238377147296e-07,0.8228543996810913,-1.749940707895803e-07]},{"children":[8],"name":"Leg4Lower.L","rotation":[-0.11090508848428726,0.11991499364376068,-0.48737218976020813,0.8577813506126404],"scale":[0.9999995231628418,0.9999997615814209,1.000000238418579],"translation":[1.9631791303709178e-07,0.8208085298538208,-4.769351491518137e-08]},{"children":[9],"name":"Leg4Mid.L","rotation":[-0.21032677590847015,0.09273893386125565,-0.42330121994018555,0.8763437271118164],"scale":[1,0.9999996423721313,0.999999463558197],"translation":[-1.720833893159579e-07,1.5146127939224243,1.4611718768264836e-07]},{"children":[10],"name":"Leg4Upper.L","rotation":[0.581340491771698,-0.03387186676263809,0.4926694631576538,0.6466628909111023],"scale":[1.0000003576278687,1.000000238418579,1.0000003576278687],"translation":[-2.6137989550534257e-09,0.8996680974960327,-2.8558396536482178e-08]},{"children":[11],"name":"Leg4Base.L","rotation":[0.4988132119178772,0.67340087890625,0.0026363276410847902,0.5456278324127197],"scale":[0.9999998807907104,0.9999998807907104,0.9999999403953552],"translation":[6.932457941033476e-10,0.3915250301361084,-7.783789612858527e-09]},{"name":"Leg3Fore.L","rotation":[-0.040254246443510056,0.0051941643469035625,-0.3734953701496124,0.9267436861991882],"scale":[1.0000001192092896,1,1.0000003576278687],"translation":[-7.186849302343035e-07,0.761729896068573,1.4940267689667053e-08]},{"children":[13],"name":"Leg3Lower.L","rotation":[-0.02293548174202442,0.03108014352619648,-0.5376279950141907,0.8422969579696655],"scale":[1,0.9999998807907104,1],"translation":[2.6879865799855907e-07,0.890315592288971,-2.3254589365251377e-08]},{"children":[14],"name":"Leg3Mid.L","rotation":[-0.10393687337636948,0.026799378916621208,-0.47722530364990234,0.8722012042999268],"scale":[1,1.0000001192092896,1],"translation":[-4.5783519908582093e-07,1.5058820247650146,6.428529530921878e-08]},{"children":[15],"name":"Leg3Upper.L","rotation":[0.22388437390327454,0.00046301534166559577,0.7424523234367371,0.6313795447349548],"scale":[0.9999996423721313,0.9999999403953552,0.9999995231628418],"translation":[8.173065424443848e-08,0.8363674879074097,-3.891337030381692e-09]},{"children":[16],"name":"Leg3Base.L","rotation":[0.48101410269737244,0.8565962910652161,-0.006458853371441364,0.18661867082118988],"scale":[0.9999999403953552,0.9999998807907104,0.9999999403953552],"translation":[1.1383551878907383e-08,0.3915250301361084,2.5693926986036786e-09]},{"name":"Leg2Fore.L","rotation":[0.04987334460020065,-0.01207074522972107,-0.4028705060482025,0.9138174653053284],"scale":[0.999999463558197,1.0000004768371582,1.0000001192092896],"translation":[2.9161077463868423e-07,0.7777483463287354,1.7455030842938868e-07]},{"children":[18],"name":"Leg2Lower.L","rotation":[0.08352424204349518,-0.04269447177648544,-0.518085777759552,0.8501694202423096],"scale":[1.0000001192092896,1.0000004768371582,1.0000004768371582],"translation":[-1.5067358560827415e-07,0.9397417306900024,-4.163759115272114e-08]},{"children":[19],"name":"Leg2Mid.L","rotation":[0.14706559479236603,-0.028868243098258972,-0.47296836972236633,0.868239164352417],"scale":[1.0000004768371582,0.9999999403953552,0.9999999403953552],"translation":[-2.2217867012841452e-07,1.5058820247650146,-6.989571943449846e-08]},{"children":[20],"name":"Leg2Upper.L","rotation":[-0.42424774169921875,-0.0005238422891125083,0.6472405791282654,0.6333192586898804],"scale":[1.000000238418579,1.0000003576278687,1.0000005960464478],"translation":[9.311328597050306e-08,0.881853461265564,-1.8038990745594674e-08]},{"children":[21],"name":"Leg2Base.L","rotation":[-0.4972459375858307,-0.7882749438285828,0.006057440303266048,0.362398236989975],"scale":[0.9999997615814209,0.9999998807907104,0.9999999403953552],"translation":[7.375939858889069e-10,0.3915250301361084,4.028271050060539e-09]},{"name":"Leg1Fore.L","rotation":[-0.01934647001326084,-0.04218549281358719,-0.4403696358203888,0.8966162800788879],"scale":[1,1,0.9999997615814209],"translation":[2.0805721590022586e-07,0.815664529800415,4.0515438115562574e-08]},{"children":[23],"name":"Leg1Lower.L","rotation":[0.15678077936172485,-0.1661715805530548,-0.47995010018348694,0.8470270037651062],"scale":[0.999999463558197,1,0.9999999403953552],"translation":[3.670676562705921e-08,0.8788074851036072,8.29251618483795e-08]},{"children":[24],"name":"Leg1Mid.L","rotation":[0.26206591725349426,-0.11672191321849823,-0.4046621024608612,0.8683006763458252],"scale":[1.0000001192092896,0.9999999403953552,0.9999995231628418],"translation":[3.601947184961318e-08,1.5125981569290161,-1.6144279868512967e-07]},{"children":[25],"name":"Leg1Upper.L","rotation":[-0.62815922498703,0.04343283176422119,0.39305803179740906,0.6701006889343262],"translation":[-1.0171092412747385e-07,1.043814778327942,1.114601104745816e-07]},{"children":[26],"name":"Leg1Base.L","rotation":[-0.536352813243866,-0.596045732498169,-0.006935927551239729,0.5975006818771362],"scale":[0.9999998211860657,0.9999998211860657,1],"translation":[7.451212979958655e-09,0.3915250301361084,-5.977072614626877e-09]},{"name":"Leg4Fore.R","rotation":[-0.0219536405056715,-0.030033595860004425,0.43784812092781067,0.8982789516448975],"scale":[1.000000238418579,0.9999998807907104,1.0000001192092896],"translation":[4.575199454848189e-07,0.82285475730896,1.3987688873839943e-07]},{"children":[28],"name":"Leg4Lower.R","rotation":[-0.11090517044067383,-0.11991491913795471,0.48737218976020813,0.8577813506126404],"scale":[1.0000001192092896,0.9999999403953552,1.0000001192092896],"translation":[5.0247152216797986e-08,0.8208085894584656,1.2523592829438712e-07]},{"children":[29],"name":"Leg4Mid.R","rotation":[-0.21032673120498657,-0.09273889660835266,0.42330119013786316,0.876343846321106],"scale":[0.9999998211860657,0.9999995231628418,1.0000001192092896],"translation":[-1.2884336797469587e-07,1.514613151550293,6.563716681284859e-08]},{"children":[30],"name":"Leg4Upper.R","rotation":[0.5813404321670532,0.03387187048792839,-0.4926694333553314,0.6466629505157471],"scale":[1,1.000000238418579,0.9999997019767761],"translation":[-3.940737158814045e-08,0.8996680974960327,1.9567494291550247e-09]},{"children":[31],"name":"Leg4Base.R","rotation":[0.4988132119178772,-0.6733996272087097,-0.0026374668814241886,0.5456294417381287],"scale":[1,1.0000001192092896,1],"translation":[-1.1682686817948706e-08,0.3915250301361084,-1.3812247345867945e-08]},{"name":"Leg3Fore.R","rotation":[-0.04025428742170334,-0.005194155499339104,0.3734953999519348,0.9267436861991882],"scale":[0.9999998211860657,1.0000001192092896,1.0000001192092896],"translation":[-7.285660217348777e-07,0.7617300748825073,-4.0205627271916455e-08]},{"children":[33],"name":"Leg3Lower.R","rotation":[-0.02293553575873375,-0.03108006715774536,0.5376282930374146,0.8422967791557312],"scale":[1.0000001192092896,0.9999996423721313,0.9999999403953552],"translation":[7.143101754536474e-08,0.8903149366378784,6.888667769544554e-08]},{"children":[34],"name":"Leg3Mid.R","rotation":[-0.10393673926591873,-0.026799339801073074,0.47722548246383667,0.872201144695282],"scale":[1.0000003576278687,0.9999998807907104,0.9999998807907104],"translation":[1.4287303429227904e-07,1.5058823823928833,9.578651827268914e-08]},{"children":[35],"name":"Leg3Upper.R","rotation":[0.2238844484090805,-0.00046323961578309536,-0.7424524426460266,0.6313793659210205],"scale":[1.0000001192092896,1.0000005960464478,0.9999997615814209],"translation":[-2.9145089897042453e-08,0.8363675475120544,-1.3412945421009681e-08]},{"children":[36],"name":"Leg3Base.R","rotation":[0.48101410269737244,-0.8565958738327026,0.006457682233303785,0.18662074208259583],"scale":[0.9999999403953552,1.0000001192092896,1],"translation":[1.187698939197901e-09,0.3915250301361084,1.396204218906405e-08]},{"name":"Leg2Fore.R","rotation":[0.04987342655658722,0.012070796452462673,0.40287071466445923,0.9138173460960388],"scale":[0.9999997615814209,0.9999998807907104,0.9999997019767761],"translation":[4.900767294202524e-07,0.7777489423751831,1.3496240569565998e-07]},{"children":[38],"name":"Leg2Lower.R","rotation":[0.08352430164813995,0.04269447922706604,0.518085777759552,0.8501694202423096],"scale":[1.000000238418579,1.0000003576278687,0.9999999403953552],"translation":[1.2208448652017978e-07,0.9397414326667786,-3.409446946989192e-08]},{"children":[39],"name":"Leg2Mid.R","rotation":[0.1470656394958496,0.028868237510323524,0.4729681611061096,0.8682392835617065],"scale":[1.0000001192092896,1.0000003576278687,1.0000001192092896],"translation":[4.8437236443987786e-08,1.5058820247650146,-2.5024842642551448e-08]},{"children":[40],"name":"Leg2Upper.R","rotation":[-0.4242475926876068,0.0005238187150098383,-0.6472404599189758,0.6333194971084595],"scale":[0.9999997019767761,1,0.9999998211860657],"translation":[3.550610472302651e-09,0.8818532824516296,4.425183419698442e-08]},{"children":[41],"name":"Leg2Base.R","rotation":[-0.4972459375858307,0.7882757782936096,-0.006056289654225111,0.36239632964134216],"scale":[0.9999998211860657,1,0.9999999403953552],"translation":[-7.2600920830723226e-09,0.3915250301361084,-5.773719280455225e-09]},{"name":"Leg1Fore.R","rotation":[-0.015208502300083637,0.04422945901751518,0.4362727701663971,0.8985980749130249],"scale":[1.000000238418579,0.9999995827674866,0.9999997615814209],"translation":[-6.20622927272052e-07,0.8156638741493225,-1.6136721114889951e-07]},{"children":[43],"name":"Leg1Lower.R","rotation":[0.15885458886623383,0.17276015877723694,0.4745163321495056,0.848382830619812],"scale":[1.000000238418579,1.0000001192092896,1.0000004768371582],"translation":[-2.3015780925561558e-07,0.8788077235221863,2.258973452740065e-08]},{"children":[44],"name":"Leg1Mid.R","rotation":[0.2600231170654297,0.12465617805719376,0.4028773903846741,0.8686419725418091],"scale":[1,0.9999997019767761,0.9999999403953552],"translation":[-2.3629894485566183e-08,1.512597680091858,-5.442473494099431e-08]},{"children":[45],"name":"Leg1Upper.R","rotation":[-0.6237055063247681,-0.03962605446577072,-0.3963613212108612,0.6725466251373291],"scale":[1,1,0.9999995827674866],"translation":[4.151442212219081e-08,1.0438144207000732,6.221015524943141e-08]},{"children":[46],"name":"Leg1Base.R","rotation":[-0.5363527536392212,0.5960471630096436,0.0069372160360217094,0.5974993705749512],"scale":[1,1.0000001192092896,1.0000001192092896],"translation":[7.877114072130098e-09,0.3915250301361084,-5.523408841412447e-09]},{"children":[5,7,12,17,22,27,32,37,42,47],"name":"Body","rotation":[-0.9999927282333374,-4.546671483751652e-09,1.1920842553081457e-06,0.003814017167314887],"translation":[-2.1589291564903364e-17,0.5146726369857788,0.22900062799453735]},{"name":"Leg4IK.L","rotation":[-2.6692541510442425e-08,-2.6692541510442425e-08,-0.7071068286895752,0.7071068286895752],"translation":[2.2291481494903564,-0.5599625110626221,-0.7613579630851746]},{"name":"Leg3IK.L","rotation":[-2.6692541510442425e-08,-2.6692541510442425e-08,-0.7071068286895752,0.7071068286895752],"translation":[2.3687760829925537,-0.5599625110626221,-0.033313095569610596]},{"name":"Leg2IK.L","rotation":[-2.6692541510442425e-08,-2.6692541510442425e-08,-0.7071068286895752,0.7071068286895752],"translation":[2.3687760829925537,-0.5599625110626221,0.6964529752731323]},{"name":"Leg1IK.L","rotation":[-2.6692541510442425e-08,-2.6692541510442425e-08,-0.7071068286895752,0.7071068286895752],"translation":[2.2556710243225098,-0.5599625110626221,1.4977319240570068]},{"name":"Leg4IK.R","rotation":[-2.6692541510442425e-08,2.6692541510442425e-08,0.7071068286895752,0.7071068286895752],"translation":[-2.2291481494903564,-0.5599625110626221,-0.7613579630851746]},{"name":"Leg3IK.R","rotation":[-2.6692541510442425e-08,2.6692541510442425e-08,0.7071068286895752,0.7071068286895752],"translation":[-2.3687760829925537,-0.5599625110626221,-0.033313095569610596]},{"name":"Leg2IK.R","rotation":[-2.6692541510442425e-08,2.6692541510442425e-08,0.7071068286895752,0.7071068286895752],"translation":[-2.3687760829925537,-0.5599625110626221,0.6964529752731323]},{"name":"Leg1IK.R","rotation":[-2.6692541510442425e-08,2.6692541510442425e-08,0.7071068286895752,0.7071068286895752],"translation":[-2.2556710243225098,-0.5599625110626221,1.5977319478988647]},{"mesh":0,"name":"Spider","skin":0},{"children":[57,48,49,50,51,52,53,54,55,56],"name":"Armature"}],"animations":[{"channels":[{"sampler":0,"target":{"node":48,"path":"translation"}},{"sampler":1,"target":{"node":48,"path":"rotation"}},{"sampler":2,"target":{"node":48,"path":"scale"}},{"sampler":3,"target":{"node":4,"path":"translation"}},{"sampler":4,"target":{"node":4,"path":"rotation"}},{"sampler":5,"target":{"node":4,"path":"scale"}},{"sampler":6,"target":{"node":0,"path":"translation"}},{"sampler":7,"target":{"node":0,"path":"rotation"}},{"sampler":8,"target":{"node":0,"path":"scale"}},{"sampler":9,"target":{"node":2,"path":"translation"}},{"sampler":10,"target":{"node":2,"path":"rotation"}},{"sampler":11,"target":{"node":2,"path":"scale"}},{"sampler":12,"target":{"node":6,"path":"translation"}},{"sampler":13,"target":{"node":6,"path":"rotation"}},{"sampler":14,"target":{"node":6,"path":"scale"}},{"sampler":15,"target":{"node":11,"path":"rotation"}},{"sampler":16,"target":{"node":10,"path":"rotation"}},{"sampler":17,"target":{"node":9,"path":"rotation"}},{"sampler":18,"target":{"node":8,"path":"rotation"}},{"sampler":19,"target":{"node":16,"path":"rotation"}},{"sampler":20,"target":{"node":15,"path":"rotation"}},{"sampler":21,"target":{"node":14,"path":"rotation"}},{"sampler":22,"target":{"node":13,"path":"rotation"}},{"sampler":23,"target":{"node":21,"path":"rotation"}},{"sampler":24,"target":{"node":20,"path":"rotation"}},{"sampler":25,"target":{"node":19,"path":"rotation"}},{"sampler":26,"target":{"node":18,"path":"rotation"}},{"sampler":27,"target":{"node":26,"path":"rotation"}},{"sampler":28,"target":{"node":25,"path":"rotation"}},{"sampler":29,"target":{"node":24,"path":"rotation"}},{"sampler":30,"target":{"node":23,"path":"translation"}},{"sampler":31,"target":{"node":23,"path":"rotation"}},{"sampler":32,"target":{"node":23,"path":"scale"}},{"sampler":33,"target":{"node":31,"path":"rotation"}},{"sampler":34,"target":{"node":30,"path":"rotation"}},{"sampler":35,"target":{"node":29,"path":"rotation"}},{"sampler":36,"target":{"node":28,"path":"rotation"}},{"sampler":37,"target":{"node":36,"path":"rotation"}},{"sampler":38,"target":{"node":35,"path":"rotation"}},{"sampler":39,"target":{"node":34,"path":"rotation"}},{"sampler":40,"target":{"node":33,"path":"rotation"}},{"sampler":41,"target":{"node":41,"path":"rotation"}},{"sampler":42,"target":{"node":40,"path":"rotation"}},{"sampler":43,"target":{"node":39,"path":"rotation"}},{"sampler":44,"target":{"node":38,"path":"rotation"}},{"sampler":45,"target":{"node":46,"path":"rotation"}},{"sampler":46,"target":{"node":45,"path":"rotation"}},{"sampler":47,"target":{"node":44,"path":"rotation"}},{"sampler":48,"target":{"node":43,"path":"rotation"}},{"sampler":49,"target":{"node":49,"path":"translation"}},{"sampler":50,"target":{"node":49,"path":"rotation"}},{"sampler":51,"target":{"node":49,"path":"scale"}},{"sampler":52,"target":{"node":50,"path":"translation"}},{"sampler":53,"target":{"node":50,"path":"rotation"}},{"sampler":54,"target":{"node":50,"path":"scale"}},{"sampler":55,"target":{"node":51,"path":"translation"}},{"sampler":56,"target":{"node":51,"path":"rotation"}},{"sampler":57,"target":{"node":51,"path":"scale"}},{"sampler":58,"target":{"node":52,"path":"translation"}},{"sampler":59,"target":{"node":52,"path":"rotation"}},{"sampler":60,"target":{"node":52,"path":"scale"}},{"sampler":61,"target":{"node":53,"path":"translation"}},{"sampler":62,"target":{"node":53,"path":"rotation"}},{"sampler":63,"target":{"node":53,"path":"scale"}},{"sampler":64,"target":{"node":54,"path":"translation"}},{"sampler":65,"target":{"node":54,"path":"rotation"}},{"sampler":66,"target":{"node":54,"path":"scale"}},{"sampler":67,"target":{"node":55,"path":"translation"}},{"sampler":68,"target":{"node":55,"path":"rotation"}},{"sampler":69,"target":{"node":55,"path":"scale"}},{"sampler":70,"target":{"node":56,"path":"translation"}},{"sampler":71,"target":{"node":56,"path":"rotation"}},{"sampler":72,"target":{"node":56,"path":"scale"}}],"name":"ArmatureAction","samplers":[{"input":7,"interpolation":"LINEAR","output":8},{"input":7,"interpolation":"LINEAR","output":9},{"input":10,"interpolation":"LINEAR","output":11},{"input":10,"interpolation":"LINEAR","output":12},{"input":7,"interpolation":"LINEAR","output":13},{"input":10,"interpolation":"LINEAR","output":14},{"input":10,"interpolation":"LINEAR","output":15},{"input":7,"interpolation":"LINEAR","output":16},{"input":10,"interpolation":"LINEAR","output":17},{"input":10,"interpolation":"LINEAR","output":18},{"input":7,"interpolation":"LINEAR","output":19},{"input":10,"interpolation":"LINEAR","output":20},{"input":10,"interpolation":"LINEAR","output":21},{"input":7,"interpolation":"LINEAR","output":22},{"input":10,"interpolation":"LINEAR","output":23},{"input":7,"interpolation":"LINEAR","output":24},{"input":7,"interpolation":"LINEAR","output":25},{"input":7,"interpolation":"LINEAR","output":26},{"input":7,"interpolation":"LINEAR","output":27},{"input":7,"interpolation":"LINEAR","output":28},{"input":7,"interpolation":"LINEAR","output":29},{"input":7,"interpolation":"LINEAR","output":30},{"input":7,"interpolation":"LINEAR","output":31},{"input":7,"interpolation":"LINEAR","output":32},{"input":7,"interpolation":"LINEAR","output":33},{"input":7,"interpolation":"LINEAR","output":34},{"input":7,"interpolation":"LINEAR","output":35},{"input":7,"interpolation":"LINEAR","output":36},{"input":7,"interpolation":"LINEAR","output":37},{"input":7,"interpolation":"LINEAR","output":38},{"input":10,"interpolation":"LINEAR","output":39},{"input":7,"interpolation":"LINEAR","output":40},{"input":10,"interpolation":"LINEAR","output":41},{"input":7,"interpolation":"LINEAR","output":42},{"input":7,"interpolation":"LINEAR","output":43},{"input":7,"interpolation":"LINEAR","output":44},{"input":7,"interpolation":"LINEAR","output":45},{"input":7,"interpolation":"LINEAR","output":46},{"input":7,"interpolation":"LINEAR","output":47},{"input":7,"interpolation":"LINEAR","output":48},{"input":7,"interpolation":"LINEAR","output":49},{"input":7,"interpolation":"LINEAR","output":50},{"input":7,"interpolation":"LINEAR","output":51},{"input":7,"interpolation":"LINEAR","output":52},{"input":7,"interpolation":"LINEAR","output":53},{"input":7,"interpolation":"LINEAR","output":54},{"input":7,"interpolation":"LINEAR","output":55},{"input":7,"interpolation":"LINEAR","output":56},{"input":7,"interpolation":"LINEAR","output":57},{"input":7,"interpolation":"LINEAR","output":58},{"input":10,"interpolation":"LINEAR","output":59},{"input":7,"interpolation":"LINEAR","output":60},{"input":7,"interpolation":"LINEAR","output":61},{"input":10,"interpolation":"LINEAR","output":62},{"input":7,"interpolation":"LINEAR","output":63},{"input":7,"interpolation":"LINEAR","output":64},{"input":10,"interpolation":"LINEAR","output":65},{"input":7,"interpolation":"LINEAR","output":66},{"input":7,"interpolation":"LINEAR","output":67},{"input":10,"interpolation":"LINEAR","output":68},{"input":7,"interpolation":"LINEAR","output":69},{"input":7,"interpolation":"LINEAR","output":70},{"input":10,"interpolation":"LINEAR","output":71},{"input":7,"interpolation":"LINEAR","output":72},{"input":7,"interpolation":"LINEAR","output":73},{"input":10,"interpolation":"LINEAR","output":74},{"input":7,"interpolation":"LINEAR","output":75},{"input":7,"interpolation":"LINEAR","output":76},{"input":10,"interpolation":"LINEAR","output":77},{"input":7,"interpolation":"LINEAR","output":78},{"input":7,"interpolation":"LINEAR","output":79},{"input":10,"interpolation":"LINEAR","output":80},{"input":7,"interpolation":"LINEAR","output":81}]}],"materials":[{"doubleSided":true,"name":"Material.001","pbrMetallicRoughness":{}}],"meshes":[{"name":"Cube","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2,"JOINTS_0":3,"WEIGHTS_0":4},"indices":5,"material":0}]}],"skins":[{"inverseBindMatrices":6,"joints":[48,5,4,1,0,3,2,7,6,12,11,10,9,8,17,16,15,14,13,22,21,20,19,18,27,26,25,24,23,32,31,30,29,28,37,36,35,34,33,42,41,40,39,38,47,46,45,44,43,49,50,51,52,53,54,55,56],"name":"Armature"}],"accessors":[{"bufferView":0,"componentType":5126,"count":1000,"max":[2.742279291152954,1.4045029878616333,2.0192716121673584],"min":[-2.742279291152954,-0.6434623599052429,-3.534085512161255],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":1000,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1000,"type":"VEC2"},{"bufferView":3,"componentType":5121,"count":1000,"type":"VEC4"},{"bufferView":4,"componentType":5126,"count":1000,"type":"VEC4"},{"bufferView":5,"componentType":5123,"count":1500,"type":"SCALAR"},{"bufferView":6,"componentType":5126,"count":57,"type":"MAT4"},{"bufferView":7,"componentType":5126,"count":120,"max":[5],"min":[0.041666666666666664],"type":"SCALAR"},{"bufferView":8,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":9,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":10,"componentType":5126,"count":2,"max":[5],"min":[0.041666666666666664],"type":"SCALAR"},{"bufferView":11,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":12,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":13,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":14,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":15,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":16,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":17,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":18,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":19,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":20,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":21,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":22,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":23,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":24,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":25,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":26,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":27,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":28,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":29,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":30,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":31,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":32,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":33,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":34,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":35,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":36,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":37,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":38,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":39,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":40,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":41,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":42,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":43,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":44,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":45,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":46,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":47,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":48,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":49,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":50,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":51,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":52,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":53,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":54,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":55,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":56,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":57,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":58,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":59,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":60,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":61,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":62,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":63,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":64,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":65,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":66,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":67,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":68,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":69,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":70,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":71,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":72,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":73,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":74,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":75,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":76,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":77,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":78,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":79,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":80,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":81,"componentType":5126,"count":120,"type":"VEC3"}],"bufferViews":[{"buffer":0,"byteLength":12000,"byteOffset":0},{"buffer":0,"byteLength":12000,"byteOffset":12000},{"buffer":0,"byteLength":8000,"byteOffset":24000},{"buffer":0,"byteLength":4000,"byteOffset":32000},{"buffer":0,"byteLength":16000,"byteOffset":36000},{"buffer":0,"byteLength":3000,"byteOffset":52000},{"buffer":0,"byteLength":3648,"byteOffset":55000},{"buffer":0,"byteLength":480,"byteOffset":58648},{"buffer":0,"byteLength":1440,"byteOffset":59128},{"buffer":0,"byteLength":1920,"byteOffset":60568},{"buffer":0,"byteLength":8,"byteOffset":62488},{"buffer":0,"byteLength":24,"byteOffset":62496},{"buffer":0,"byteLength":24,"byteOffset":62520},{"buffer":0,"byteLength":1920,"byteOffset":62544},{"buffer":0,"byteLength":24,"byteOffset":64464},{"buffer":0,"byteLength":24,"byteOffset":64488},{"buffer":0,"byteLength":1920,"byteOffset":64512},{"buffer":0,"byteLength":24,"byteOffset":66432},{"buffer":0,"byteLength":24,"byteOffset":66456},{"buffer":0,"byteLength":1920,"byteOffset":66480},{"buffer":0,"byteLength":24,"byteOffset":68400},{"buffer":0,"byteLength":24,"byteOffset":68424},{"buffer":0,"byteLength":1920,"byteOffset":68448},{"buffer":0,"byteLength":24,"byteOffset":70368},{"buffer":0,"byteLength":1920,"byteOffset":70392},{"buffer":0,"byteLength":1920,"byteOffset":72312},{"buffer":0,"byteLength":1920,"byteOffset":74232},{"buffer":0,"byteLength":1920,"byteOffset":76152},{"buffer":0,"byteLength":1920,"byteOffset":78072},{"buffer":0,"byteLength":1920,"byteOffset":79992},{"buffer":0,"byteLength":1920,"byteOffset":81912},{"buffer":0,"byteLength":1920,"byteOffset":83832},{"buffer":0,"byteLength":1920,"byteOffset":85752},{"buffer":0,"byteLength":1920,"byteOffset":87672},{"buffer":0,"byteLength":1920,"byteOffset":89592},{"buffer":0,"byteLength":1920,"byteOffset":91512},{"buffer":0,"byteLength":1920,"byteOffset":93432},{"buffer":0,"byteLength":1920,"byteOffset":95352},{"buffer":0,"byteLength":1920,"byteOffset":97272},{"buffer":0,"byteLength":24,"byteOffset":99192},{"buffer":0,"byteLength":1920,"byteOffset":99216},{"buffer":0,"byteLength":24,"byteOffset":101136},{"buffer":0,"byteLength":1920,"byteOffset":101160},{"buffer":0,"byteLength":1920,"byteOffset":103080},{"buffer":0,"byteLength":1920,"byteOffset":105000},{"buffer":0,"byteLength":1920,"byteOffset":106920},{"buffer":0,"byteLength":1920,"byteOffset":108840},{"buffer":0,"byteLength":1920,"byteOffset":110760},{"buffer":0,"byteLength":1920,"byteOffset":112680},{"buffer":0,"byteLength":1920,"byteOffset":114600},{"buffer":0,"byteLength":1920,"byteOffset":116520},{"buffer":0,"byteLength":1920,"byteOffset":118440},{"buffer":0,"byteLength":1920,"byteOffset":120360},{"buffer":0,"byteLength":1920,"byteOffset":122280},{"buffer":0,"byteLength":1920,"byteOffset":124200},{"buffer":0,"byteLength":1920,"byteOffset":126120},{"buffer":0,"byteLength":1920,"byteOffset":128040},{"buffer":0,"byteLength":1920,"byteOffset":129960},{"buffer":0,"byteLength":1440,"byteOffset":131880},{"buffer":0,"byteLength":32,"byteOffset":133320},{"buffer":0,"byteLength":1440,"byteOffset":133352},{"buffer":0,"byteLength":1440,"byteOffset":134792},{"buffer":0,"byteLength":32,"byteOffset":136232},{"buffer":0,"byteLength":1440,"byteOffset":136264},{"buffer":0,"byteLength":1440,"byteOffset":137704},{"buffer":0,"byteLength":32,"byteOffset":139144},{"buffer":0,"byteLength":1440,"byteOffset":139176},{"buffer":0,"byteLength":1440,"byteOffset":140616},{"buffer":0,"byteLength":32,"byteOffset":142056},{"buffer":0,"byteLength":1440,"byteOffset":142088},{"buffer":0,"byteLength":1440,"byteOffset":143528},{"buffer":0,"byteLength":32,"byteOffset":144968},{"buffer":0,"byteLength":1440,"byteOffset":145000},{"buffer":0,"byteLength":1440,"byteOffset":146440},{"buffer":0,"byteLength":32,"byteOffset":147880},{"buffer":0,"byteLength":1440,"byteOffset":147912},{"buffer":0,"byteLength":1440,"byteOffset":149352},{"buffer":0,"byteLength":32,"byteOffset":150792},{"buffer":0,"byteLength":1440,"byteOffset":150824},{"buffer":0,"byteLength":1440,"byteOffset":152264},{"buffer":0,"byteLength":32,"byteOffset":153704},{"buffer":0,"byteLength":1440,"byteOffset":153736}],"buffers":[{"byteLength":155176,"uri":"data:application/octet-stream;base64,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BgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAEADgAUAAEAFAAHAAoABgATAAoAEwAXABUAEgAMABUADAAPABAAAwAJABAACQAWAAUAAgAIAAUACAALABEADQAAABEAAAAEAEwAUgAsAEwALAAfACIAHgArACIAKwAvAEgAUAAkAEgAJAAnAEsAVQAhAEsAIQAuAFYATgAgAFYAIAAjACkAJQAYACkAGAAcADcAQgA8ADcAPAAxAD0APwA0AD0ANAAyAEYAOwA1AEYANQBAADAAMwA5ADAAOQA2AEQARwBBAEQAQQA+ADgAOgBFADgARQBDAB0AGgBPAB0ATwBXACgAGwBUACgAVABKAC0AKgBRAC0AUQBJABkAJgBTABkAUwBNAFgAWwBhAFgAYQBeAF8AYgBtAF8AbQBqAGwAbwBpAGwAaQBmAGUAaABdAGUAXQBaAGAAawBkAGAAZABZAG4AYwBcAG4AXABnAHAAcwB5AHAAeQB2AHgAegCFAHgAhQCDAIQAhwCBAIQAgQB+AH0AfwB0AH0AdAByAHcAggB8AHcAfABxAIYAewB1AIYAdQCAAIgAiwCRAIgAkQCOAJAAkgCdAJAAnQCbAJwAnwCZAJwAmQCWAJUAlwCMAJUAjACKAI8AmgCUAI8AlACJAJ4AkwCNAJ4AjQCYAKIAqACrAKIAqwClAKcAtAC3AKcAtwCqALIArACvALIArwC1AK0AoACjAK0AowCwAKYAoQCuAKYArgCzALYAsQCkALYApACpALoAwADDALoAwwC9AL8AzADOAL8AzgDBAMoAxADHAMoAxwDNAMYAuQC7AMYAuwDIAL4AuADFAL4AxQDLAM8AyQC8AM8AvADCANIA2ADbANIA2wDVANcA5ADmANcA5gDZAOIA3ADfAOIA3wDlAN4A0QDTAN4A0wDgANYA0ADdANYA3QDjAOcA4QDUAOcA1ADaAOkA6wDxAOkA8QDvAPAA8gD9APAA/QD7APwA/gD4APwA+AD2APUA9wDsAPUA7ADqAO4A+gD0AO4A9ADoAP8A8wDtAP8A7QD5AAABAwEJAQABCQEGAQcBCgEVAQcBFQESARQBFwERARQBEQEOAQ0BEAEFAQ0BBQECAQgBEwEMAQgBDAEBARYBCwEEARYBBAEPASkBHQEjASkBIwEvASgBJgEZASgBGQEbAS0BKwElAS0BJQEnASEBHwEsASEBLAEuARgBJAEqARgBKgEeARwBGgEgARwBIAEiATEBMwE6ATEBOgE4ATcBOQFFATcBRQFDAUQBRgE/AUQBPwE9AT4BQAE0AT4BNAEyATYBQgE8ATYBPAEwAUcBOwE1AUcBNQFBAUkBSwFRAUkBUQFPAVABUgFdAVABXQFbAVwBXgFYAVwBWAFWAVUBVwFMAVUBTAFKAU4BWgFUAU4BVAFIAV8BUwFNAV8BTQFZAWABYwFpAWABaQFmAWgBagF1AWgBdQFzAXQBdwFxAXQBcQFuAW0BbwFkAW0BZAFiAWcBcgFsAWcBbAFhAXYBawFlAXYBZQFwAYkBfQGDAYkBgwGPAYgBhgF5AYgBeQF7AY0BiwGFAY0BhQGHAYEBfwGMAYEBjAGOAXgBhAGKAXgBigF+AXwBegGAAXwBgAGCAZEBkwGZAZEBmQGXAZgBmgGlAZgBpQGjAaQBpgGgAaQBoAGeAZ0BnwGUAZ0BlAGSAZYBogGcAZYBnAGQAacBmwGVAacBlQGhAakBqwGxAakBsQGvAbABsgG9AbABvQG7AbwBvgG4AbwBuAG2AbUBtwGsAbUBrAGqAa4BugG0Aa4BtAGoAb8BswGtAb8BrQG5AcABwwHJAcAByQHGAcgBygHVAcgB1QHTAdQB1wHRAdQB0QHOAc0BzwHEAc0BxAHCAccB0gHMAccBzAHBAdYBywHFAdYBxQHQAekB3QHjAekB4wHvAegB5gHZAegB2QHbAe0B6wHlAe0B5QHnAeEB3wHsAeEB7AHuAdgB5AHqAdgB6gHeAdwB2gHgAdwB4AHiAfEB8wH5AfEB+QH3AfgB+gEFAvgBBQIDAgQCBgIAAgQCAAL+Af0B/wH0Af0B9AHyAfYBAgL8AfYB/AHwAQcC+wH1AQcC9QEBAgkCCwIRAgkCEQIPAhACEgIdAhACHQIbAhwCHgIYAhwCGAIWAhUCFwIMAhUCDAIKAg4CGgIUAg4CFAIIAh8CEwINAh8CDQIZAiACIwIpAiACKQImAigCKwI2AigCNgIzAjQCNwIxAjQCMQIuAiwCLwIkAiwCJAIhAicCMgItAicCLQIiAjUCKgIlAjUCJQIwAkkCPQJDAkkCQwJPAkgCRgI5AkgCOQI7Ak0CSwJFAk0CRQJHAkECPwJMAkECTAJOAjgCRAJKAjgCSgI+AjwCOgJAAjwCQAJCAlICVAJZAlICWQJXAlgCWgJmAlgCZgJkAmMCZQJgAmMCYAJeAl0CXwJTAl0CUwJRAlYCYgJcAlYCXAJQAmcCWwJVAmcCVQJhAmkCbwJzAmkCcwJtAm4CewJ/Am4CfwJyAnoCdAJ4AnoCeAJ+AnUCaAJsAnUCbAJ5AnACagJ2AnACdgJ8An0CdwJrAn0CawJxAoICiAKLAoICiwKFAocClAKXAocClwKKApICjAKPApICjwKVAo0CgAKDAo0CgwKQAoYCgQKOAoYCjgKTApYCkQKEApYChAKJAqcCrQKhAqcCoQKbAqgCnQKZAqgCmQKkAq8CqQKlAq8CpQKrAqMCrgKqAqMCqgKfApoCoAKsApoCrAKmApwCogKeApwCngKYArECtgK6ArECugK1ArcCwwLHArcCxwK7AsICvQLBAsICwQLGArwCsAK0ArwCtALAArgCsgK+ArgCvgLEAsUCvwKzAsUCswK5AskCzwLTAskC0wLNAs4C2wLfAs4C3wLSAtoC1ALYAtoC2ALeAtUCyALMAtUCzALZAtACygLWAtAC1gLcAt0C1wLLAt0CywLRAuIC6ALrAuIC6wLlAuYC8wL3AuYC9wLqAvIC7ALvAvIC7wL1Au0C4ALkAu0C5ALxAucC4QLuAucC7gL0AvYC8ALjAvYC4wLpAgcDDQMBAwcDAQP7AggD/QL5AggD+QIEAw8DCQMFAw8DBQMLAwMDDgMKAwMDCgP/AvoCAAMMA/oCDAMGA/wCAgP+AvwC/gL4AhEDFwMbAxEDGwMVAxYDIwMnAxYDJwMaAyIDHAMgAyIDIAMmAx0DEAMUAx0DFAMhAxgDEgMeAxgDHgMkAyUDHwMTAyUDEwMZAykDLwMzAykDMwMtAy4DOwM/Ay4DPwMyAzoDNAM4AzoDOAM+AzUDKAMsAzUDLAM5AzADKgM2AzADNgM8Az0DNwMrAz0DKwMxA0IDSANLA0IDSwNFA0YDUwNXA0YDVwNKA1IDTANPA1IDTwNVA00DQANEA00DRANRA0cDQQNOA0cDTgNUA1YDUANDA1YDQwNJA2cDbQNhA2cDYQNbA2gDXQNZA2gDWQNkA28DaQNlA28DZQNrA2MDbgNqA2MDagNfA1oDYANsA1oDbANmA1wDYgNeA1wDXgNYA3EDdwN7A3EDewN1A3YDgwOHA3YDhwN6A4IDfAOAA4IDgAOGA30DcAN0A30DdAOBA3gDcgN+A3gDfgOEA4UDfwNzA4UDcwN5A4kDjwOTA4kDkwONA44DmwOfA44DnwOSA5oDlAOYA5oDmAOeA5UDiAOMA5UDjAOZA5ADigOWA5ADlgOcA50DlwOLA50DiwORA6IDqAOrA6IDqwOlA6YDswO2A6YDtgOpA7IDrAOvA7IDrwO1A64DoQOkA64DpAOxA6cDoAOtA6cDrQO0A7cDsAOjA7cDowOqA8cDzQPBA8cDwQO7A8gDvQO5A8gDuQPEA88DyQPFA88DxQPLA8MDzgPKA8MD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TmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+cw9Rrv9jabWlCWo/0XvPPt5waq/JcWm1pQlqP9F7zz54BNKqTmtptaUJaj/Re88+jT7qr0Z4abWlCWo/0XvPPhnLKDCae2m1pwlqP9F7zz7t5U+vDU5ptaUJaj/Se88+goncLxOCabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4MKI+vempptaUJaj/Re88+WtPpL1ZeabWlCWo/0XvPPgDKKDCYe2m1pQlqP9F7zz5ssc+vzDBptaUJaj/Re88+NwGPsPxnabWlCWo/0XvPPuaqzi7ueWm1pQlqP9F7zz5TyIEve2hptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1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4G1iP+fAELztE0K9GXDnvvwEZD/Ubm27I9lMvcau3r5aKWY/BMwvuVYXVL21b9a+XhZoPyUpjjptxVa98bnSvvXsaD+J6Z652tVTvVoT1L5EoWg/nweCu5kqTL01Zde+DeRnP8KSFrxBaEG9XIfbvjjyZj9jLXm8ASo1vSVf376GCmY/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/fRmbvAekLb0AjOC+bMJlP2ROkryp1y+9SxjevoVaZj/IVIa8Gdcyvf2L2r7UMmc/zpJyvNAdNr0XVta+0i1oP13HWbzWQjm9ePDRvm0taT9Ir0S8M/07vd3jzb4ME2o/FLM0vFQhPr37xMq+N8BqP2DQK7zTXz+9SjDJvpwWaz+PoyK8iLNAvd5Oyr5r2Go/nfUQvDs+Q72B+M2+7gpqP/l78Lvg/ka9nPTRvpklaT8LXL27jU5LvQiK077sxmg/aiaHu9RrUL2HxNG+gilpPxcwI7v/CVa9XUTNvqMkaj8tNmS633pbvS0+yb74/mo/V0U+OoIFYL36X8q+cLxqP5jSBDqSW169UzHUvpuQaD/7lRm7t/ZUvfex4r4BJ2U/A6fYu2buSL1aJO6+zERiP+YWNLzQJD696Hrzvo3eYD8FFGm8m+s2vdKA9L5pmmA/sNN3vBjtNL2WpPW+ZktgP/UiXbzFMDi9WxP5vq9XXz9sxCW8QDI/vb+h/b6pC14/NuKuu1uvSb2nrAC/wfJcP8egsjmhUVe9VtIAv//QXD9lXdA7xbVnvZrp/L7tG14/gelHPMzMeb0rrPO+wpVgP6vRjjx0f4W9vAjpvptKYz8cd648nhCMvcuC4L7yV2U/f6m6PCucjr01Kt2+AR9mP3+pujwrnI69NSrdvgEfZj9/qbo8K5yOvTUq3b4BH2Y/AACAPwAAgD/8/38/AQCAPwIAgD8CAIA/udIUPy29Cj0qP/y+s4slP1zTFD9txAo9gz78vl2LJT991xQ/VPQKPQE5/L6ViSU/gOIUP/RzCz1RKfy+PIUlP8zTFT+bgRY9vLf5vu+OJT8IuhY/vKQgPatk9r4X8yU/N9oXP9NWKz2/WfG+drsmP8ghGT81+zQ9UgPtvtQTJz+aiho/dPU8PVNP6L50ZSc/VyMbP67oPz1+DOa+npwnPzGIGz9lfkE9vHrkvrHGJz/pHBw/82xEPQDS4r4+ySc/7zMcPwvYRT0XMeO+9ZEnP+1lHD/3UEY99lvivt2qJz/8lxw/ZrJGPcCD4b6FxCc/Rd4cPyq8ST0Z1eG+xGMnPz1MHT+Ak0s9jbDgvsNcJz8yJB0/3txJPU6D4L6akyc/qwccPyHIPT29Pd++kxYpP0DZGD/Sqhw9+oHbvsdLLT8KuQ0/b/GCPIa3375tcTU/CrUAPx/8e7uAs+y+pPs6PwM89T44ekK7yzn5vhsCOz9unvg+rZ+Aus3y/L6voDg/IEz7PjwtJLl46P2+RmM3P232/j6kiVw6jZ3+vhbfNT+NcQI/FpAnO94//75eiDM/PRsGP+OHxjva9f+++owwP8j5CT8vWjM84yMAv+ZoLT/VnA0/KtiIPEH+/76phyo/WaAQP15HvDwCMf++3DwoPw7jEj9uG+o8DPX9vsmuJj+/UBQ/P+oEPVnE/L5I0iU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT9+zRM/gGT+PJeX/L7KXCY/rmwRPxGCyzwkAP2+DFwoP0DXDj9ZEZo8rfn8vvqdKj/Alw0/9YuEPJnW/L7muCs/5ZcPP1tmqTwtfQC/VXYoP7T+Ez94pwA90N8Ev935ID8bkhg/GlA0PQP0CL+L4xg/3LQbP6YFXD1xkQq/o/0TP/mVHD9YbWg9w38Iv67oFD860Bs/6KxePXAFBL+IvBk/5WMaP5UATD0yq/6+1yEfPxiaGT+kvUE9CUn6vuipIT/s4xg/Qcc5PUHC+r7BMCI/C0kXPzJIJz1PlPu+mXMjP4ScFT/SzBM9Zhr8vp3bJD+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/udIUPy29Cj0qP/y+s4slP7nSFD8tvQo9Kj/8vrOLJT+50hQ/Lb0KPSo//L6ziyU/QhwVP/LvDT1b7/y+ZAMlP7t9FT/ZNBI9fob9vjltJD8wKhU/bgwOPQZ5+76KhSU/aIcTP1Ic9jzvJvW+dl8pP5/ZED/fsrs8/EzsvtTQLj8mQA4/5aWMPH3u5b6sEDM/qxsMP8XTXjz7TOO+ppg1PyQkCj/b6S08uQ7ivoZ+Nz+oqQg/2T0IPFrR4b7WrTg/VNsHP+194zt1QeK+kiQ5PwOlBz/AQtQ7uPnivksUOT/95gc/C5LgO1Cx474sqzg/d8YIPwrpBzw6kuS+I783P1owCj+qsi48KrjlvkNRNj+x8As/OJJgPJIZ577zhTQ/P9UNPzhfjDzUp+i+IYQyPx2tDz/SuKg87U3qvqF3MD/SRxE/uNPBPB3u6760ky4/dHISP/WF0zyNX+2+OhYtP23pEj/aatk8dmbuvtJULD/xmhI/Zl7TPP2c7r6lhiw/6L4RP7YkxjxmFO6+inMtP4piED8X37g8v47tvhTHLj8HeQ4/5qGyPIW27b40SzA/DPoLPzXmtzzI9O6+XtwxP5EeCT8LrM08URnyvkn/Mj+bcAY/4w/3PEcN+L7l8DI/uvIDPyEMEj1/QP6+s40yP2TeAT8O0Sk9RHICvzaZMT+sgwA/Ha07PZ3+BL/vnjA/ewgAP61jQj0H9gW/CzYwP3sIAD+tY0I9B/YFvws2MD97CAA/rWNCPQf2Bb8LNjA/3l9Xvu/tvb3vutg+EFhgP59gV74V5r29NrvYPg5YYD9eZle+CK+9vY292D7fV2A/TXZXvnQZvb1ExNg+RVdgPzN1Wb60KK29NsjZPvwsYD9SyFu+y1abvXcE2z7g718/edpevv1ag70eotw+JZdfPwI+Yb7Q4lm9h6LdPphhXz9LhGO+ofIqvcZ23j7NMF8/j4JkvjvtFb0c094+pRhfP2krZb4c4ge9OA7fPgsIXz9Y2mW+wJDsvK0o3z40AF8/LrdlvruG7Lz9+94+pw1fP3wLZr4mcN68ZxffPv4EXz+GX2a+CVjQvEQy3z5D/F4/gERmvqkxybyF5d4+1BJfP7G1Zr4RcLC8aOXePskQXz+2xGa+S460vA8S3z7UA18/Ei9nvkVS0byxQuA+NqpePwJbaL5mYA+9YTXjPmLBXT9w3WO+p0SuvW5f5D5i21w/tR9Tvo06Gb6iPdo+uDReP4E9RL7fMTq+WBnPPnYkYD+omkK+DP85vlzezT5jhmA/CRBDvhpgN76TLM4+k5BgP2guRL4G+zK+GfXOPpmLYD8RREa+0BUrvgVy0D6ueGA/WDpJvpTnH76MKdI+8GtgP1hsTL51lxK+/u/TPlhkYD/3nU++E1YEvqqP1T6pYGA/1aZSvojq7L3739Y+O19gP58ZVb6dANW9P97XPiBdYD9awla+XzvEva6A2D7dWWA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/92Va+e33IvRjb2D6MM2A/h6lVvolv373zDNk+2OJfPzFjVL4J9fW9vR/ZPjSTXz+DxFO+PQYAvlId2T7eb18/vBBSvpaM+r2IJdc+FhxgP0H+Tb7CH+y9E2DSPqS3YT+lpUm+ou3XvebYzD7aimM/VRxIvlF/wb3woMk+kqhkP4SBS76rW6y9k1vLPjdaZD+4ZFG+l+6avTQe0D6xI2M/BppWvklMj71zx9Q+K99hPyzIWL4cHIu9887WPjdNYT9dkFi+XiCTvZQk1z7DJ2E/sRVYvqKspL3A1dc+E9RgP4GZV75SE7a9EXfYPgV/YD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/3l9Xvu/tvb3vutg+EFhgP95fV77v7b2977rYPhBYYD/eX1e+7+29ve+62D4QWGA/ClRavmpJvL0bmNs+Qn1fP189Yr5YGbi9MUfjPnEeXT+4Lm6+emWyvVTw7j5KTVk/N8J8vg6XrL1MT/0+oTdUP41Thb5eEKm9vpoFP8TeTj+lv4i+jH+rvRcZCT9v+Us/+F6IvjuQtL1y2Qg/DhVMP3mvhr6AYcC9SFkHP+wwTT8iboS+J93LvRRNBT85uE4/0DqCvqR71L3ERwM/ojhQP7CVgL5oPdm9QMABPwtbUT+p1H++HcvZvRccAT9U2FE/6OB/vv6+1b26EQE/au5RP+EegL7aTM69JB8BP+L8UT+xeoC+nKbEvX9NAT9e91E/mQyBvpv6ub3dqQE/6s5RP9Tjgb5Ueq+98UgCP0NvUT9PGYO+U1+mvcZLAz/WulA/4NOEvqfwn71L5QQ/7oRPP1xLh77Njp29r1oHPw6NTT/uyIq+akievTnsCj/Sjko/gY6PvvX+n710yQ8/+ENGP229lb78j6K9LP4VP7NnQD+1Hp2+zTCmvd4yHT97/Tg/iC6lvv80q72W1SQ/N1AwPypKrb4t3LG97UwsP77fJj9k0LS+YT+6vXX+Mj8QbB0/svC6vuPgwr3IYzg/mQQVP3GVv77wZsu9M1s8P01ADj8Hc8K+YcbRvZzGPj+D1wk/M2/DvoQx1L1Kmz8/RkYIPzNvw76EMdS9Sps/P0ZGCD8zb8O+hDHUvUqbPz9GRgg/OSLjvfyV9b3aiPk+j5dbP6gf471Wj/W94on5PmyXWz+CDeO95GD1vdWQ+T6Nlls/BNzivdzi9L1vo/k+Q5RbP0hS3b35nee90En7PgBsWz8o09a97+jYvVm7/D7YV1s/2MDNvc0xxb0bJf4+lFxbP6I+xb2/krK96jn/PkprWz/9Kry9CQafvb/x/z7KkVs/V/m3vfhRlr2EDAA/Eq1bP1Mmtb1kf5C9NhIAP8PCWz8Oq7G9XhWJvYkfAD9c2Vs/F76xvVD4iL30LQA/ANFbP+dPsL2qCYa98SoAP5feWz/p4K69DRqDvXcmAD/c7Fs/K06uvZVkgb1OQAA/rONbP17bq71XOHi960EAP572Wz9wMKy9ujJ6vfszAD90+1s/enuuvff4g72ulv8+CSFcP8detL3bLJa9YeP8PtamXD+DG9a9zNTtvcxm8T75L14/ieP4vfmwLr69ddo+fDthP94IAr5kDEu+pd7LPjz2Yj89wAK+PMVKvs6XzT4MkGI/FNACvndjSL6Xvs8+tTNiPzywAr66bkS+qunSPpawYT9KLAK+KGU9vr8k2D522GA/l/oAvuxRM76f3N4+OsNfP/zK/b1yfye+5//lPtaZXj/fBfi9Pfkavq6l7D5lhl0/7zDxvVDdDr7uLPI+/qdcP4Bg6r2amAS+nTr2PpgMXD+MK+W9z+z6vZqw+D5GtFs/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz9cuua9lZT+vdRC+D74u1s/Bx3uvYYdCb5XFvU+ziFcP6mz9L1F3BK+KGzxPl6jXD8lafe9/UQXvgaa7z6c5lw/SOf2vcyCFL6+2vE+kmlcPylb9L1qhw2+IZH2PrZvWz+K5+69oiAEvkIc+z5gm1o/UInnvfQg9L3iTv4+AyxaP7vd3706VuK9Afr/PuEbWj+zkti9zfjUvZP//z57bVo/qLXSvTzyzL2nv/4+cgBbP9ZB0L3STsq9e9z9PoZVWz9wWdO9kSPRvYta/T6gVVs/VfzZvScT4L37A/w+TmNbP+hY4L2w4+69gGD6PjKDWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/OSLjvfyV9b3aiPk+j5dbPzki4738lfW92oj5Po+XWz85IuO9/JX1vdqI+T6Pl1s/V7fmvZJZ873g8/0+dE1aPwNO77272e29IEQEP5QWVz9OlPm9jBbnvfuKCj8CCFM/AkkBvseI4b0e2w8/jF5PP5UQBL6d59+9w6wSP2xNTT8n+AS+ttzjvQWZEj/DQE0/t/kEvhSy7L2upBA/wHpOP0/UBL63hPe9LQ0OP4oUUD8WfgS+Q+QAviFUCz+9u1E/QPoDvjmyBL4lAgk/FiFTP89zA756xQa+Hn0HP9cLVD9DLwO+D/cGvp0gBz+FR1Q/he8CvpsXBb4Hygc/ufBTP0FrAr7QsQG+Fu8IP8NaUz+SzwG+8Y/6vdJ5Cj/CiVI/PE0BvrW08L20Vgw/WYBRP3YbAb761ua913UOPyQ/UD8heAG+ewvevQfKED9GxU4/qKQCvrqG171aRRM/kxJNP5TdBL4os9S9WdMVP4krSz+h8ge+mQHVvcV0GD/hEUk/BFoLvqgA173uBRs/sOpGP3i1Dr4+Cdu9OTMdPzf7RD/asRG+S3bhve63Hj8ggkM/vA8Uvi+b6r1oXx8/+LFCP/22Fb6miva9nyIfP3WUQj9f8Ra+ARUCvpFFHj+J8kI/SQYYvoi+CL5WUR0/s2FDP6PBGL7fwg6+HFQcP8/eQz8SVBm+SPwSvhK0Gz8WJUQ/BJAZvv2KFL4Nfhs/OzpEPwSQGb79ihS+DX4bPzs6RD8EkBm+/YoUvg1+Gz87OkQ/CNizvAEJ9ryhLeA+nPVlP3PHs7xMAPa8ey7gPm31ZT/9UrO8SMP1vNgz4D5F9GU/bBayvMgd9bzmQeA+QfFlPzoRkLzDtOO8LC3hPjDCZT+UhVO8AWDQvKep4T50rWU/idXXu6ZTtrwJtuE+crRlP8CeMLr9d52858XhPra2ZT8qL7U7yimDvJFz4T70zWU/3DsIPOjebrybGOE+buRlPzXMJjz9Hl+8gMvgPgX3ZT/TOk08UPFKvFyV4D54A2Y/IlZNPLKUSrzB0eA+u/RlPzi7XDx5mEK8TZ7gPtcAZj89JGw8EJY6vKNn4D6jDWY/kiV0PCHONbyQw+A+6vZlPwSxhzwLSCe8mJXgPuwAZj/6XoU8hg8qvLdi4D6MDWY/UZlqPPFIPbyd8d4+cWhmP/ifFjzfdG+8AZjaPg90Zz9M+Im8XlDsvNz5zj5r/mk/s95IvXQmP73BGr0+TURtP9jWg7136GS9VaazPnW6bj+7kIS9arVkvR9xtj5eMW4/mouCvRyTYb3kvbg+RsdtP73Tfb1/Vly9Dum7Pvc0bT/hVHC9IwdTvcgAwT5RRWw/Eu5bveidRb2dh8c+/AhrPzjHQr218zW9OmnOPn+uaT9b7ya9VWQlvRO11D4WZmg/7NEKvSJmFb2TyNk+5VVnP5Y75Lxb5Qe9nGHdPtaQZj/5H8G8Cw39vNd63z6CHGY/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT92QMq8AvoAvbPg3j6NPmY/z2z6vL38Db3amts+DPRmP3J6FL3l6Bq9fMvXPnHDZz+5xR69TcUgvVXl1T40KWg/sOYZvdwLHb0hoNk+zVBnPz9hDL2wlhO9ebjhPittZT8yEvC888gGvUPd6T5ldGM/KhrAvLc18rzu7u4+bzViP3lokbzxntq8D0rvPhIsYj86plS8J33JvBQA7D4zEmM/fW0fvLFtv7xDauc+VURkPzPPC7yoJry8KQjlPnDfZD9X3y68oljFvMF15D6TAGU/9CZ7vMpc2bzT9OI+OldlPy4No7y3H+28zh7hPifAZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/CNizvAEJ9ryhLeA+nPVlPwjYs7wBCfa8oS3gPpz1ZT8I2LO8AQn2vKEt4D6c9WU/AKa0vAsR9LypweM+hBRlP8gxtrz0Zu+8ytXrPu4HYz9F8ba8lTjqvFzG8z7o7WA/0E+2vGE757xyQvc+3fpfP+JQtrxY3ui865j0Pja1YD//cby8KrjvvEKb7z5ICWI/GazLvI7r+rx0l+s+cBBjPwAw37weAwS909jnPlf+Yz+FwfG8SwUKveZx5D5y0WQ/Qjj/vF9PDr050uE+oHFlP6YdA71dhRC9J1LgPmLMZT9ZWgO9qJYQvTlP4D7szGU/Onj/vA0rDr1p2eE+4G9lP/bw8byayQm9ClfkPj/YZD+vceC8LCAEvXaN5z4aEWQ/flLNvDa1+7y3Qus+zyVjPyQGu7xlU++8XjvvPhEjYj/BKay8KZrkvM4w8z7uGWE/Wo6jvC9D3bydvfY+myVgP901pLwIcdu8OUX5PoRyXz/FGay8VqPevBhN+j5zJl8/sUi2vLn85LwS9/g+ZoJfPyBcwbwMEe+8D/3zPqLbYD8Ve8y8aCv9vBX16j6tOWM/N7zXvNWPB72CYN4+sVZmP/6847ykMxK90krPPhrLaT+yufC8AzwdveYhwD7392w/qeP9vFFnJ70EqbI+ioxvP2hdBb3GCjC9iuGoPkZHcT+SKQq99/g1vRjJoj7AS3I/Ev4LvVIjOL3Xp6A+/KNyPxL+C71SIzi916egPvyjcj8S/gu9UiM4vdenoD78o3I/9kFlPiPP8rldET6/FaIhPw1FZT7Bn++5SxE+v+WhIT9RWmU+RMDZuU4QPr8soSE/ypNlPq3Gnrk3DT6/tJ8hPxdhaz6NCSQ7PEo9v5H+IT8/rnE+JXyuOwkgPL/fwyI/ei96PuA9DDxwSjq/wA4kP7hOgT4iBDc84tY4v7jeJD/k5YU+O19ZPENIN78driU/+fWHPtIzZjzhgja/RRsmP+tXiT7Kl208Wfg1v71pJj9KHos+pP53PCh1Nb+omSY/JyeLPnGbejyrpjW/k2EmP+LZiz65T308/1s1v0qNJj9ejYw+Y7l/PAUQNb/tuSY/mPeMPvQZgzwyTDW/ZGEmPyE7jj6UIoY8IfQ0v9F7Jj9i/o0+3VKEPHTSNL+zrSY/2VCMPrgFbzwo4DO/ww8oP/iThz5PoSg8KBUxv6P5Kz+aXGA+HeO2u31vLr8axjI/xQsgPqn3s7wJGy+/jlI2P8F6+T03N7W8TxEzvxEvND+4lgA+3Su3vCSDNb8ljDE/ZD4FPkPkuLyqcja/Rl4wP7oaDD4Ob7q8ZW03v2cDLz/2nBc+zHm6vBbEOL93/iw/hAImPndRr7z4Zzq/fmYqP7qFNT52yJe8CvY7v020Jz9fmUQ+2yRuvJUmPb8xUCU/MrFRPtxuH7zE3z2/z30jP/n4Wz4ZXKa75iI+vytaIj8oxWI+EUHhujYePr/0yiE/9kFlPiPP8rldET6/FaIhP3mVbT4urNw7NN9LvzL5Dj+tJW8+tdfpO3tmSb8uRhI/A2ByPhO4+TsncUO/8NMZPztudT5gyO87Osg8vxGlIT8tzXY+D9DgO4uIOb+9PCU/i311PjHY0jvp9Tm/O+EkPybacT44tKc7yxA7v2T3Iz8eU2w+MIo4O7aKPL9TxyI/9kFlPiPP8rldET6/FaIhP38RXT6Neo+7zFk/v5nTID9N+VQ+FUADvPE0QL+ZeiA//KdOPghNLLw0pUC/uHUgP04ZTD5BQzu87cVAv72BID93ylE+fTsTvBLDQr+uoR0/kbBdPt65RLvt0ka/lG8XP73HaD7Cwmw7MWlKvxWHET95lW0+LqzcOzTfS78y+Q4/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/9kFlPiPP8rldET6/FaIhP/ZBZT4jz/K5XRE+vxWiIT/2QWU+I8/yuV0RPr8VoiE/MuZkPirSObpNsT2/3xoiP1bmYz5ASbW6kKo8v6diIz/fVWI+h6YduyYfO78lSSU/eEZgPsUIcrszLzm/AqAnP53SXT71eai7dvw2v244Kj9xJVs+SdzYu32sNL8P4iw/739YPhGKAryRaTK/7WovP+o7Vj4m4RO84WIwv5KfMT9XzFQ+peMdvFHMLr+OSjM/tLZUPnMiHrzE4S2/pi80P1rXVT42GRi85nstv798ND8Tg1c+5LwQvIpALb9WljQ/U5BZPlAbCbwpIC2/Zo40P6PXWz6kfwK83Qwtvwl1ND9FN14+u9/8u7b8LL8uVjQ/nIhgPrcs/LuP6Cy/lTs0Px3ZYj5qdQK8nqksv1pJND9Sk2U+BH0MvEtCLL92dDQ/DgBpPo03HLys6Su/7oE0P8ZIbT7a6C+8qN4rv+4xND/T/XE+3rVGvI8ELL/qpzM/jxV3PpQTXbytkiy/Za4yP1NnfD5FpG68z7UtvyIaMT9x+IA+U/B1vEW0L7/UnS4/2ziDPmPserzdizG/IVEsP3IVhT4uH3y84FQzv7sYKj8XZYY+tsV3vEQONb9DACg/t3OHPnqzb7z/yTa/zOYlP2stiD6NW2q8iAQ4v8ljJD8pmog+/4VmvG/NOL94ayM//76IPqYLZbwnFTm/pBIjP/++iD6mC2W8JxU5v6QSIz//vog+pgtlvCcVOb+kEiM/zNzUvTmK27zlVvQ+k0hfP6Td1L1+adu86Vb0PpdIXz/g49S9IoTavINX9D6NSF8/BPXUvToV2LyHWfQ+VkhfPyn71r2FBJa8Zun0PrYmXz/cQ9m9CY8ZvGul9T54814/ETrcveKhJjuwjfY+xqpeP92j3r01QV48dM72PoiIXj9Q4uC9NazMPIfN9j4pb14//czhvWU39jz2x/Y+dmJeP3xq4r3V7wg9+cD2PtBZXj+SIuO90DcaPUKG9j7bW14/vBTjvUY9Gj38V/Y+32heP4Fl471oIyE97FH2Pl5kXj9ttuO9EggoPSJL9j7bX14/YMbjvU2HKz0T6/U+gXdePzFL5L2pmzc97q71Pll8Xj+AQOS9XJk1PW7m9T7Wbl4/bwDkvWWNJz1wZfc+ohBeP3Wz4716nAE9lUL7Pg0VXT8UPN69NbyavG6TAD/ckFs/Pe/PvQYNs71lBfw+BS9cPyCswr0Q5Pu9L0DyPlj+XT/Tn8C9xI77vcaM8D46fV4/ttrAvdTC9b0Wi/A+ypZeP9eswb1KBuy9GejwPvCkXj95PsO9MY3avWiw8T6IsF4/ZQ/GvSD/wb08gPI+EsleP/Ysyb3Z6qS9PjXzPkPqXj+LaMy9HA+GvdW78z6EDF8//qnPvYK9UL3NEfQ+cyhfP9FX0r0RHB69nj70PsQ7Xz/iLdS9WNj1vMxR9D63RV8/zNzUvTmK27zlVvQ+k0hfP13Yyb3kn+W8aA3jPl/yYz/iRs29FdbCvGPC5j53/2I/3D/UvVTobLx9q+4+A+VgP4R/2r1IS6u7NCX2PrDNXj+MIt29jWKNumxe+T5Z3l0/DoLcvf96RLu98/g+ff5dP+PG2r3Hugi8Btf3PqFSXj/VJ9i9yx2HvPs79j5Qx14/zNzUvTmK27zlVvQ+k0hfP4Qy0b3CCB29/l7yPl7DXz8yu829KlhJvaaV8D4HKGA/FSLLvesEar37Re8+kWtgP80byr0PwHa9RsPuPmuEYD+YCsq93gxjvW9B7T6T/2A/7NTJvWVvNr3Afuk+9CNiP7DDyb34PQi9RS3lPpteYz9d2Mm95J/lvGgN4z5f8mM/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/zNzUvTmK27zlVvQ+k0hfP8zc1L05itu85Vb0PpNIXz/M3NS9OYrbvOVW9D6TSF8/ljXVvYOT3Lyp8fQ+lhxfP2Mr1r0rkN+84Z72PtOhXj8rnte9TYfkvK0p+T6B5V0/Y2/ZvTx367zsXvw+D/RcPy6F271ICvS81AcAPwzZWz+IzN297Wf9vKEJAj/Nnlo/eDrgvdYRA72TJAQ/I05ZP6jL4r1aIga9i0wGPxzuVz+6geW9z7EGvdR4CD9VhFY/hGzovSNzA73epAo//RNVP5t+671wF/q8294MPyWTUz9dve69b5jqvFZBDz987VE/hjbyvTJc2bzR1hE/shhQP4v89b2dese8Sa8UP04FTj9HJvq9Qiu2vMjhFz+anEs/2sX+vVO/prwLiBs/lsJIP5D1Ab4I6pe8xqwfPzBjRT9P2gS+gXWHvJVrJD9wV0E/6xMIvj4Ha7zl1Sk/a3g8P8WNC754KUS8iNgvPw65Nj9KLg++ZV8cvD4nNj+GQzA/eOQSvlCX6ruxkjw/5S8pPz2XFr4wNaG7FeFCPxOtIT9XHBq+X9A3u83kSD+37Bk/H2odviBskLqMVk4/6FMSPxduIL6QVVI5JTRTP3H/Cj/1FiO+XrSJOntKVz80YAQ/Kjclvjsz3jqHd1o/lcn9PpfEJr7lDhE7ssNcP1Fx9T6zuSe+GX0lO5opXj8lLfA+vw0ovvNVLDtxo14/UFnuPr8NKL7zVSw7caNeP1BZ7j6/DSi+81UsO3GjXj9QWe4+TuO7vKSb/rwCogk/xaBXP9HUu7w0gf68MaIJP7KgVz8Sb7u8b8j9vE+jCT9GoFc/fFq6vDbT+7w1pgk/O59XP0GnnLySMse8pMYJPy+eVz+CuXa8JjmNvE+1CT8Yulc/Z6odvGYWALz9WQk/qQJYPzFLmLtuGIQ66eoIPzhOWD9apPk5CcQmPBxECD9OtFg/i0w1O3kUaTwr5Ac/auxYP39/jDt8rIo87ZwHP08VWT8ZR8o7fb+mPD5LBz+QQlk/EQnKO9wepzyWWgc/7zhZP5bC4juoOrI8NzEHPxtQWT81avs7Kli9PG4GBz/tZ1k/wKQDPJuwwzzWDgc/FWFZPxgCGTzQmNc8Ts4GP4uDWT/dmxU8Te7TPG7KBj/+hlk/sq77O0twujzapwY/LaNZP1TPdzvci208mwgGP/MSWj+QBpS8n0HcvOhaBD8z+Vo/m8Y6vSCsqL22KPw+Nn5dP4OSbL0sZ+W9LtryPoMMXz/o5W29eQrlvYfS9D6Wgl4/GwlrvUb8372HsvY+khVeP8G0Zb35jde971T5PgmAXT8daVu9oZbIvSWL/T43kFw/uMdLvTRTs72eXwE/7GRbP65xN71ak5q9tv0DPzkyWj+XQCC9bKeAvVI9Bj/8J1k/eHkIvWrtT71z5wc/umNYP9ol5rwifCa9IvMIPyHrVz9pfse8rO8JvSx6CT8HsVc/TuO7vKSb/rwCogk/xaBXPwJRw7zkv/a8xaoMP9epVT/lhrS8Y8XcvLeYDD8UwFU/6YuSvLRnpby3Agw/ajVWP8rXXrxeZWC8EtQKP+UHVz/sqj68USkxvHIYCj9ChVc/kn9MvF7MSbwnHAo/vIBXP2kZc7zBQ4e81hsKP7l5Vz9h85a8jMm7vBz9CT/rflc/TuO7vKSb/rwCogk/xaBXP6mJ5LwfEyW9t/YIP1zqVz8exQS9xuxIvZ4SCD+EUVg/GfMRvf2yY73/Pwc/gbJYP4H1Fr2qOG69H+QGPwLdWD9kuw+9y+9cvYEbCD9lMVg/roL8vKYcNr23Ygo/T+1WP+zZ1byNCA696hYMP+b6VT8CUcO85L/2vMWqDD/XqVU/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/TuO7vKSb/rwCogk/xaBXP07ju7ykm/68AqIJP8WgVz9O47u8pJv+vAKiCT/FoFc/25u8vGXM/7xapAk/wZ5XPxa2vrxylAG9Rq8JP1GWVz+LKMK8z0gEvR3FCT/zhVc/3OHGvAjyB71G5wk/vmxXP2ywzLwLXQy9zhkKPypIVz9rNtO8NiERvd5lCj+PElc/zOnZvKeXFb0I2wo/QMJWP5Ye4Lzo3Bi9h48LPzhJVj9BF+W8YNsZvX+fDD8YlVU/xyPovBZUF72mKg4/3I9UP87K6byd8xG9ZyEQP40/Uz9rL+u8cnILvZ9UEj/+vlE/bansvKU4BL0KuBQ/ghNQP9Sg7rxoifm8VD0XP2tETj+CifG8h1/rvNHTGT+QW0w/hdH1vHBo37wQahw/iWRKP2XJ+ryQ19S88+weP5xuSD9HYP+8w17KvJs7IT/zlUY/sbwBvQifwLyKNyM/avZEPwdaA717ILi8hs0kPy2kQz8TlAS9YOmwvAz5JT8sp0I/rnQFvc5Dq7yIsiY/FglCP+7/Bb2Xgqe8df8mP1fHQT/Z7gW9HEGmvCngJj+b4kE/GpkFvdLLp7zlWCY/nVZCP4QQBb3Ba6y8+00lPzQ5Qz95cwS93d+yvB8vJD+qKUQ/u/UDvVlPuLxwUyM/y99EP6pVA73fvLy8j40iP66CRT816wK9kJ+/vOMOIj8+6kU/QMcCvRuiwLyR4yE/hw1GP0DHAr0bosC8keMhP4cNRj9AxwK9G6LAvJHjIT+HDUY/ruEkvaczqrvKOr8+Ez9tP5nYJL0DFaq7Iju/Pgg/bT/MmCS9iT+pu/k8vz7YPm0/VesjvZH8prt5Qb8+bj5tP7RXEb1YHlW7PS+/PsFObT8APPm8sUShutaYvj4YeW0/RNLBvFLRvjqKQb0+lcptP8G8jrwdoII7wgm8Pu4Qbj8l5jO8m+bYO4Rtuj5jZ24/9AgFvPrR/juihbk+7ZVuP9GMy7spFAw8ddy4PkS3bj890Xq72UEcPH0uuD4X2W4/etB6u82PHDzkY7g+xs5uP880PLu07yI8xgO4PkThbj8dP/u6rFEpPOOgtz4r9G4/uxW8ut0mLTxF2Lc+aOluP+r1fDnFsjg8bla3PuABbz/acRy4jno2PJY4tz60B28/HSYBu9sSJzylWbY+4TJvP3BY57uix/w7UICzPpe8bz8m7Ay9ubKNu0nyrT7/m3A/xYyQvYHhqLwOTKM+LeZxP5xetr1s2vK8hHqdPvxmcj98Y7e9M0jyvD1ToD6B7HE/tvS0vfTo67xBXKI+f55xP/iPsL3mWOG80RClPp04cT/Pbqi9K7/OvE9PqT6Ql3A/XVWcvZtptLykla4+7MtvP0WVjb0dNJa8vuyzPsj4bj8DA3u9V/NtvNiBuD44QW4/mytbvdA1M7yf2bs+h7xtP7aQP73tmwK8oeu9PgZtbT+MKyy9yNTCuyDwvj6iSG0/ruEkvaczqrvKOr8+Ez9tP7uMLL0yPpq7g0zNPoZEaj9P+yG9oSd5u2mHyz4Xr2o/BcsKvc0M+7qiwsY+R8NrP56o57yE2hi5PdXAPvgJbT+T/tK8yUglOr3GvT5xrG0/A+PbvO1TUTnMAr4+cJ5tP9Kk9LwWcoO6Ro2+PqB8bT89Jw29Alk6u5ETvz7xVm0/ruEkvaczqrvKOr8+Ez9tPz5GP7148wC8Zry+PndDbT8GzFe9Bv0qvJqpvT7UY20/uLZpvey1SrwKgLw+i4xtP8WmcL2NQ1e8Q/a7PjegbT/P9Wa9DCNCvLg6vz7sA20/GmRQvUs2E7y3usU+xsRrPz8eOL2Poca7fjDLPs2waj+7jCy9Mj6au4NMzT6GRGo/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/ruEkvaczqrvKOr8+Ez9tP67hJL2nM6q7yjq/PhM/bT+u4SS9pzOqu8o6vz4TP20/tRglvYCUq7tR/74+4kptP428Jb2mb6+7G2S+PpJpbT9W1ya9lpi1u0CEvT58lW0/Qm8ovS3ivbvxd7w+mMltPzB0Kr2zzse7ZFu7PjkAbj/qtCy9U17Su1ZTuj5CMm4/6tsuvez327sUj7k+2VZuP7VyML0faeK7zEi5Pj9jbj/H6jC98v3iu1HEuT7aSm4/WqAvvfWD2rugR7s+GABuP3DxLL31rcq7V6C9PkCLbT/wtim9yNK3uwtPwD6nA20/NiAmva3porvQLMM+TnBsP6RjIr2CH427bQXGPnrbaz+OvR69M81vu92RyD4rVGs/mXAbvX5XSrsFe8o+s+1qPwgRGL2DISi7u4HLPj23aj/k7BO9ZjQGu2wqyz7gzGo/bPMOvYN3zLp39Mg+pklrPxYuCb07Y5W6bKfEPj41bD+2yAK92H9NurqSvj4Jd20/yu33vJIuA7rZ6bY++/puP7wU6rzaQqC5fRmuPlyicD/KLt28eQWEuSzlpD7xQnI/wbHRvIuyq7mynJs+Pc1zPxp+yLyWWAq6+bCSPh8wdT8p8cC8Kz9Uuopnij40ZXY/Pta6vL8wibpXvYM+w093PyOytrzEa6O6wxt+Pjftdz9WLbS8G4S0ujJUeD5MS3g/c1CzvDl3urpWV3Y+H2t4P3NQs7w5d7q6Vld2Ph9reD9zULO8OXe6ulZXdj4fa3g//TbZvuFQCTqNsSW/OCEiP6Y12b4R5Qo69LElv0IhIj8HLNm+CZYWOki0Jb8YIiI/uxHZvnvhNjo1uiW/1yQiP3kR1r7NBJU7kPAlvxPrIj8YjNK+1NETPFfiJb/7GiQ/c5fNvuMzczy0lSW/yPAlP6T2yL6MQp48o5Ylv4FRJz8+68O+ERm/PEaaJb+mwig/npXBvsoRzTzqjCW/sXcpPyIAwL4IIdY8Wn4lv0D2KT+MG76+dp7fPJuSJb+CZyo/ODq+voIi3jwTvyW/LDQqP8xpvb4GceI8T7Mlv0V4Kj+7l7y+tKvmPIemJb9zvSo/TWy8vm3i5Tzy9yW/lXoqPzUXu74xq+s8ygkmvxTFKj+uMLu+FB/sPPPbJb966io/tt+7vt5s7zwnlCS/X/UrP8Wjvb4XGfo8xdwgvyLzLj/er86+l/+1PK6rF7+MYTI/brPpvlfhujuctQ6/f4UxP+SA+76uZk67MWINv2duLD+u6Pu+TJT9u3UOEL+BCyo/3776viVnGrxKfxG/nD0pPyG1+L666zS8QEUTv09zKD9kEvW+Fi5TvD/9Fb8oXyc/AH/wvu+VY7z7ZRm/Ce4lP1Je677oElq83uwcv3Z3JD/FFua+zSc4vOsZIL94RyM/2zPhvo2o/7tZoCK/hX8iP6sh3b63B3y7gV4kv7omIj8JStq+Y2k5uoFdJb+qGiI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/Wztq+mMeGugLSJL/UeyI/wULevqJehLsIqyK/l3kjPxC34b6jcc67rzIgv7e4JD9FT+O+dBjmu/r4Hr9/WyU/54/jvnHlJrwxACK/3UoiP02X476i/IS8dZoov69gGz9OeOK+HvqlvOc4L783RRQ/3KPfvuNNm7ytFTO/mLIQPw/y2r4tlTu8Vb8yv6DxEj98TdW+YfKhuiqPL7+Gxhg/20nQvofZ9zs5tyu/sb4eP44Xzr5+1Tg8utMpv6p0IT8a+c++pyEePMVVKb9cYCE/IOfTvlGHwTuhDyi/JHEhP2mY175rUQ07jYAmvzPXIT/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI//TbZvuFQCTqNsSW/OCEiP/022b7hUAk6jbElvzghIj/9Ntm+4VAJOo2xJb84ISI/963YvpC9yDpGRiW/K7wiP9w9175aboo7LBokvwtjJD+tItW+J/0EPJxFIr+Z3CY/rpbSvn9sUDz24B+/5u4pP//Wz77uxI885Q0dv8BZLT/SJs2+ule1PIf7Gb9h1jA/S9HKvut81Txf5Ra/IBs0P64dyb4qnOw8cxYUv1bdNj+Wesi+k4r6PDHDEb/m4Dg/KNrIvo+U/zxLMRC/iP85P5nzyb4cvv48bzEPvyR5Oj94iMu+uWT8PHRfDr+lrDo/MXLNvh16+Dzn5w2/24I6P7Fdz75wNfI8APcNvzvxOT9h4dC+vuHmPILZDr8N2jg/1Y3Rvmbu0zwd2hC/8B03P3WG0r4cHK88Aa0UvwnIMz+0ktS+oAJ2PAnCGb8x4S4/b8TWvqzeDzxzmB6/M9gpP5Rt2L4fwoU7eTYiv8rdJT9qZNm+Uu+QOnGNJL+AOiM/ae3ZvuFAW7r3QSa/oU8hP7w12r5FIx67BuYnv0GBHz+eF9q+OPFdu9JIKb+bEh4/FvbYviFLD7uWRim/03gePwDU1b40J9g6bf0mv7LsIT9jANC+phzPO3QQI789tic/X8XIvgO/+jsMfSC/CVUsPwKTwr5mo5A7Fi8hv7l2LT83ob6+W5Zzua9uI7+jdCw/Cv68vu6KObuJ4SS/8oUrPwr+vL7uijm7ieEkv/KFKz8K/ry+7oo5u4nhJL/yhSs/YZgWPiF97DzlKPI+7UReP/OXFj77new8tSjyPvZEXj80lRY+QYPtPO8n8j4NRV4/840WPiXx7zwzJvI+LkVeP5/5FT6Vcxg9Wk7yPpksXj9HaBU+wek7Pa6T8j6gBF4/iaAUPt+Daj381/I++s1dPxeVEz5Xtoo9SoLyPg+/XT/dbxI+IbWgPQPh8T7Yu10/yPMRPiFfqj3KkPE+9rldP/KdET4kxrA9RljxPtS4XT9SEhE+YtG4PfHj8D7ww10/mfUQPpLnuD2nufA+T9BdP7fHED4UE7w9QpvwPsLPXT8hmRA+cTy/PTh88D5Cz10/L0cQPnz9wD10GPA+iOddP2XVDz5WlsY9x7bvPsvyXT9CBRA+VZXFPSDw7z7y5F0/nEwRPuWKvj2bfvE+FYRdPyB/FD6GVqs9Ypz1Pqd+XD+Zfxw+MawJPe9q/j4JhFo/xnAgPmITF71uBf4+9GpaPzGyHz5dM5i9Dm72PvP+Wz/LQB8+XN2WvUhp9D6wl1w/YQ4fPqkxkL1dHPQ+FcFcP0rTHj75PIW9wg30Pk/jXD9fbB4+XJVjvVcf9D6MDl0/fF4dPm+kLb3NG/Q+fkxdP0gYHD5GNd28tefzPryRXT8ioBo+PVYzvNiM8z490l0/0iIZPiBiijuLG/M+rwVePx7YFz4Ztoo8c6byPrkpXj+07xY+DQvSPD5M8j48Pl4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj/JdRc+BcnAPBv88j5sDF4/kVMZProcQDylw/Q+bopdPwojGz43JBu5G3z2PjcBXT+I8Bs+iRK2uww+9z7QwFw/LF8aPqNNILuGZfQ+xJ1dP895Fj59tr47qJPtPt+fXz/hlhE+m36PPNnX5T72y2E/EbUNPu3C+jxtIOE+TQtjPwHQDD7JQzA9YS3iPoCvYj8xJg4+Qd1ZPeB/5j5qZmE/2PcPPnZDdT2hx+o+HRxgP83TED6hEX89g6XsPniKXz8ewhE+xRtqPaSJ7T42W18/lsMTPtJnOz3edO8+9u5eP4a4FT4c/gs9SVPxPs17Xj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/YZgWPiF97DzlKPI+7UReP2GYFj4hfew85SjyPu1EXj9hmBY+IX3sPOUo8j7tRF4/Qt8WPmu48jyP0/I+rBFePx2tFz4mTAE9B7X0Ph2AXT+9+Bg+684LPaCf9z6Vm1w/oLcaPsKuFj2gZvs+uG5bP8fdHD5t8x89t+D/PmMDWj+DYB8+AAAmPTl2Aj9oYVg/4zoiPtyVJz0SOwU/W4xWP1GRJT4KzCM9mUMIPz98VD/UMik+SisaPZyLCz9YMVI/jd0sPp5sDT2x6w4/XcJPPy+IMD7Dlv08A1YSP9Y1TT8DLjQ+6nzcPHjCFT/6j0o/sZI3PiAqvDzJ/Bg/pvlHP/pFOj5wcKI8u5wbP9nNRT8+6Ts+xF+WPPo3HT8NcUQ/EBM8Pitqnzy9XB0/Uk9EPyf7OD5bKbQ8blkaPwPaRj93mzI+lerEPIEzFD/F0Es/HGorPoeR0Dw4QQ0/Mg5RP6UqJT7mRNg8jjEHP3tTVT9CoSA+qb3ePJvOAj86QFg/kGcePtE15zxutQA/hJlZP7vdHj5bBfU8WFEBP9IzWT837CE+ADQEPR+PBD8iE1c/S70nPuXnDz11kgo/1e9SP8IZMj62Thw93osUP0xzSz+oPkI+AWEpPa3dIj/RJz8/naxVPnarOD3RBzM/gqIuP9wAZz4b00k9F/dAP+1/HT+PbHI+81lZPTQJSj/wbRA/bKV2PlQAYT3xPk0/F1ILP2yldj5UAGE98T5NPxdSCz9spXY+VABhPfE+TT8XUgs/yQ6rPWfgLj1FoQQ/tKRZPyQSqz187S49EKEEP7+kWT9qKas9CEkvPXifBD8npVk/VmirPYFBMD39mgQ/U6ZZP7XAsT3aako9dvUDP8jfWT8SW7g9pihnPQUMAz8OOlo/z4LAPcqahj12lgE/I9FaP9Cdxz0POZg98SYAP75hWz/zZ849JlSqPUb5/D6GClw/HzzRPb1Gsj1aV/s+Ul5cP5QK0z2Skbc9DjT6PuSYXD+wUNU9YTu+PZjW+D5X3Fw/J2zVPS1Jvj0V8/g+t9NcP6FC1j2O7sA9bVf4PhjzXD+5FNc9s5PDPVq59z70El0/27LXPSkExT2Aofc+HhJdP3kv2T3itck9f6D2PixDXT8319g9TuHIPemv9j4+Q10/v1zWPSQawz3yDfc+WkddP5gXzz1surM9xJD3PpByXT+hSKg9VXlFPcul/T42CV0/0xZXPcZW+bsVI/4+fdJdPzxUFz2P9R+9inX7PqaSXj+8QRo9WVUgvapm/T49A14/n8QgPXEZFr0QwP4+36JdP+AXKz38yAS9oT8APxwlXT+4JT09u63LvB+NAT8bZFw/ZuBVPTJhaLycAgM/IIBbP2m5cj0NxvK65EAEP2eqWj+3Eog9Y0A0PKgHBT9yClo/tLKVPVxFvTxWRQU/ILFZP+jgoD3U/wc90BkFP+2VWT+QV6g9lFkkPYzJBD/TnFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT9Zr6Y916IdPXfvBD++j1k/ZsGcPaso7zyMZAU/131ZP0OEkj24p6I8I40FPyWTWT91zI09ZnZ/PC+GBT++qVk/pz+RPQMHkTydQAY/HCtZP9q7mT29WcA8lXwHP9tFWD96gaQ90XkDPbgOCD+nt1c/pV6vPdK9LT39nAc/Ir9XP8/XuD2gIlg93k0GP6dKWD+bp789Ebp7PWZYBD8BQVk/SCPDPeHXiT1eSQI/ZFVaP8IFxD3KO449z0sBP4XdWj80iMA9nbWFPcf3AT+DmVo/Bke4PZPSZT3tQAM/7RtaPxlTrz00FUA9tEMEP4zBWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/yQ6rPWfgLj1FoQQ/tKRZP8kOqz1n4C49RaEEP7SkWT/JDqs9Z+AuPUWhBD+0pFk/FLCrPZOYMT0DiwQ/E65ZP+FUrT3Yjjg9AVcEP7bCWT8wjq89+eFBPfIVBD/62lk/Ae2xPRO/Sz1J2AM/oe9ZPxIYtD3fhlQ9RLQDP+f1WT9P2rU96d5aPQfJAz863Vk/ICe3PTivXT3qPgQ/iY5ZP1AbuD2k0ls9TUIFP7PuWD9/5rg9hyZVPTL9Bj8r4Fc/oAq6PZfZSz0LZAk/AWBWP3xguz0L6UA9TCkMP3KYVD93Xbw9Em80PXTlDj/ay1I/xze9PRIuKD2faRE/qBhRP0BNvj33TB49y6ITPx6MTz+XEsA9fk0ZPZCCFT/EME4/4P3CPQjlGz0w9xY/lhNNP/NTxT3hfCE9m5sXP9qMTD8G58Q9KWIkPUrkFj+QE00/ytTBPU3pJT2KsxQ/97VOP75bvT2cIic9TqoRP/LrUD82pbk9abcoPeUJDz9+xlI/zf24PehcKz3lKA4/ql5TP8hrvT1n6i89/i4QPwXrUT8WYcY9Vbo2PW29FD9oj04/iSjSPVMaQD1IwBo/NeBJP0gf3j12p0w9lK4gP/nuRD9gPOc9qeFdPYLGJD/ER0E/+r7rPWw2dT0+8CU/HxZAP7OF7D12Cog9HNAkP7/lQD/Gj+w9LDKTPQA5Iz/2HUI/8kjsPXwUmD3vaSI/rb1CP/JI7D18FJg972kiP629Qj/ySOw9fBSYPe9pIj+tvUI/D0hMPaPERTwQRc4+8O9pP9tQTD2d1EU8w0TOPvjvaT/djUw9N0VGPB5Czj5S8Gk/9TJNPdl2Rzx7Os4+YPFpPysDXj3ismc8kuLMPrEraj8o5289dJmFPOXqyj5ChWo/0mmDPVM0nTx+ysc+qRRrP9DmjT1bNLM8bPXEPiqRaz8VXZg92+jJPMC5wT7bHWw/SNWcPUvi0zyTH8A+uGNsP9TDnz2Mi9o8TgG/PlOUbD9HhaM9DfXiPLS/vT7AyGw/cKqjPTIH4zzM8b0+Sr5sP1cYpT1VYOY8d1i9PjbYbD+vg6Y9WbrpPLm8vD5/8mw/5YOnPb6P6zzf0Lw+NetsP10eqj00iPE89ui7Pl4QbT8fjKk9WnjwPE7fuz4wFG0/LoilPTAb6TyBjbs+jjFtP9+Bmj01utU8DjG6PmuYbT+oHEk9kh5jPO8dvD5tvG0/ws04PM1tXLuijrs+lC9uP4fOFbycCFu8RBe7PrBCbj/k5Qy832JcvEYXvj7Iqm0/L0Pau+mOT7wOxr8++VVtP++5ZrvVsjm879bBPrvsbD9ligM7d6QSvKnkxD5hTWw/sg4gPHePtrvPZMg+Po9rP1hNmTxJF+C6BZHLPsrYaj9dhOQ8EZAWO3/EzT5+Tmo/dScVPQ4bxjsyyc4+VABqP9/YMT2g/BU8M9zOPmLmaT87L0U9lNQ4PE1+zj4Q6mk/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT/+g0A9558wPK56zj4m72k/5CMmPcnxATyzes4+RQVqP48zCz3jx6U7PdjNPvA7aj+unP08LUx1OzRZzT5BX2o/nZQHPXdRjzsGRdA+87RpP2W8HT0A68c7eifWPvpQaD/NJDs97ZYOPKcH2z4AFmc/Bs9ZPa+ZQjy4Wtw+EqhmP/kCdD1bgnc8fzvZPvtHZz8gPoM9dgaSPAI00z75lGg/rj2IPVsMoTw96Mw+cu5pPw+0iT2qk6Y8lfLJPo+Oaj/NaYQ94PObPPLyyj49ZWo/bNJwPV+rhDzJvMw+Ih9qPwDkVz0641o8Re7NPnP3aT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/D0hMPaPERTwQRc4+8O9pPw9ITD2jxEU8EEXOPvDvaT8PSEw9o8RFPBBFzj7w72k/DLRNPe8rSTzwzM0+9AhqP75OUT225FE8mZDMPpRKaj+++1U97JldPIPFyj5gqWo/zaRaPQ4Majz1n8g+ThprPzdkXj3JPXU81V/GPi+Qaz+fmmA9XYV9PKdVxD7Q+ms/N+9gPYzDgDxU4MI+ikdsP78IXz07qX88bFnCPj1lbD/2TFs9j953PBMXwz4wQmw/20pXPSHlbDxRIcU+HdprP44JUz0f6V88M9jHPmNMaz/itk09u/dQPNVbyj5LyGo/nkVIPQknQjxYdcw+RVlqP4BURD1mJTY8Tm7OPnHuaT9YxUM9PRUwPEqv0D4Vb2k/Yp1IPepmMzxPpNM+K8BoP7XbUD0s6zk8WFTYPhukZz8QbFc9NlI8PJlR3T6Eb2Y/gBJaPdvpPDwHft8+k+ZlPzZiWT2kcD08PJvePi8eZj/XKVg9wMs+PM3N3D5MjmY/6MZZPRnDQTx1wtw+V49mP99FYT0TVEc8BMjgPpeOZT9SBG49LSZQPIQS6D70rmM/5Cx9PT4jXTxKze8+yJphP911hD1AYXA89HnyPsbUYD+xi4U9zIqGPJky6j4s/2I/j3yBPR+9mTx1P9Y+LuVnP4iZdz1hT648Z4u+Pl4LbT/IWW895729PLVJrD6IiHA/pgttPZIxxDy61qU+8KpxP6YLbT2SMcQ8utalPvCqcT+mC209kjHEPLrWpT7wqnE/Kqsfv9ZOIr3g78q+BCwsP6eqH7/cRyK9vfDKvkIsLD/Iph+/mRQivf31yr5+Liw/Apwfv3qHIb2IA8u+ADUsP1AyHr/l4hC91r7LvktZLT8CbRy/Ikr8vA0XzL6U5y4//d0Zv0+hzLxFPMy+hy0xPweZF79bgai8T+rMvkv3Mj9MHxW/p++IvPDGzb660DQ/z/YTvyYUd7zYDs6+s7E1P90rE7+dwmW8ljbOvmpMNj8yRxK/AJRTvOqkzr5Y5jY/LWYSvzVdVrwp6s6+w7k2P2j8Eb+9JU68XvfOvisLNz+IkRG/mhFGvCUDz75wXTc/d5kRv/OER7w3kc++3S43P5X5EL9mcjy8FuTPvsyWNz+79RC/tJg7vB+Uz76BsDc/stUQv7yCNbw6WM2+fWo4P4JTEL86/SK8QdnGvvmVOj/g6RS/oMKavIO+s74Gxzs/j/wdvytlI70g0Zy+gUQ5P2FmJr/kLFu9EYKVvqEVMz/Vxye/zlxvvbXzmr4mhTA/s80nv7P3dL1CNZ6+l74vP7qFJ7/x/Xi9y1OivtQMLz8D6ya/enZ7vXqiqL7bHi4/8hYmv/yveL0ZYLC+6wEtP9AUJb+zEW69Byi4vkkALD/XwiO/nh9fvZsYv76ucis/bWUiv1DgSr3XdMS+c1QrP5oWIb9pvja98B3IvgGYKz/VEiC/Qe0nvco6yr6b+ys/Kqsfv9ZOIr3g78q+BCwsP99JKL/CgWy9BB7ivvSbGz99ECa/lktUvfDu3r68PR8/Jsggv5PwH71ed9e+0UQnP4T5Gr9lkdu87nXPvm8/Lz+6Khi/XOSvvPCsy75t1DI/6SMZv8Dxv7x+lMu+EQIyP3t1G79pfuq8rzfLvooKMD8WPR6/3t8SvZRsyr7GsC0/EM8gv7rmMb3xPsm+sosrP5vSIr+JpEu9RhrIvj3cKT+dRiS/6kNevUs+x74xnig/by0lv9OUab07wca+0NEnP9B9Jb8IcW29vp3GvqeHJz88Gia/YudvvdqUy75xaCU/gz4nv0Zicb1fodW+zwUhP7AMKL+jp269qXrevnEoHT/fSSi/woFsvQQe4r70mxs/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/Kqsfv9ZOIr3g78q+BCwsPyqrH7/WTiK94O/KvgQsLD8qqx+/1k4iveDvyr4ELCw/XVwfvyM+IL0p0sm+ososP5WDHr/Zoxq95LzGvr95Lj9KOh2/OUYSvQUGwr7s+TA/rpkbvwjDB70a/ru+Qww0P46/Gb+RLve8hf+0vuRuNz/w0Be/TXbcvIV5rb5p3Do/9v0Vv3J+wLww9aW+Qgs+P6p4FL/lh6W8WxufvoC1QD98VRO/fD+PvJC5mb5xrkI/pb4SvxgLgbxPppa++btDPxeNEr9vO3K8PoWVvrEZRD/2chK/DGhlvOhUlb5gN0Q/RGMSv0djW7z7C5a+4iBEP9g1Er/rFFS8H6OXvvf0Qz9X1RG/aLVPvG8bmr7SwUM/hy0Rv00LULy4jp2+uY5DP1lHEL8321i89e2hvuBTQz9YPA+/XwVvvPwyp76M+kI/lRcOv5DCi7zrq62+E2NCP8XDDL+Qlqe8fPO0voGrQT+lTgu/jWDKvDLkvL5Cy0A/q8gJv7Se8ryvS8W+cbw/PwJECL9WLQ+9igbOvmd2Pj+uywa/goglvVLq1r7g+Tw/yyAFvz/iOL0kTd6+AvM7P2E0A7+6LUi96s/jvjGXOz9qSwG/7WRUvej3575Ilzs/T3n/vg6+Xr2DaOu+Noo7P5Am/b7+rma9HgHuvoh4Oz8gsfu+H45svSgo8L4yPzs//jD7vgGMbr1t5PC+OCs7P/4w+74BjG69beTwvjgrOz/+MPu+AYxuvW3k8L44Kzs/wSGFPsNL/z3yRc4+UF9ePzchhT5gU/89cUXOPl9fXj+6HYU+ZIj/PVNCzj6rX14/dxSFPgIMAD4yOs4+W2BeP4lMhD7oegc+1aXNPg9aXj/pf4M+a38PPjYUzT6NSV4/g2KCPi/qGT4nO8w+CTZeP0sIgT6lbSM+4/bKPiRGXj+NJn8+MxMtPrxayT7XZF4/2s59PldJMT7encg+BHNeP/LkfD6+ETQ+4h3IPrh8Xj/Kins+epA3Phlcxz4Jk14/n1p7PpabNz6TPMc+65xeP7zhej77+jg+U/vGPtmhXj+0Z3o+Mlk6PqG5xj7apl4/LMp5PiseOz5eWsY+0bxeP0LBeD58ij0+w8fFPijPXj84HHk+/xk9PlD+xT6qwl4/4JJ7PicEOj5xe8c+22peP90FgT4JkDE+7n7LPpB5XT/iK4s+K1gDPtXA1z4WCls/j0CTPglAdT0xjt8+6a5ZP60nlD71Iao8DWbdPt6NWj/qHJM+V7iwPH432z4QRls/Zo2SPiG8zTxsb9o+pIlbP0LYkT7q+/w8fJ7ZPkrPWz+dkpA+Hd4nPWN02D7cMlw/OsaOPj1XYD0N4NY+iK5cP8OljD4ggpA9kgLVPksuXT+wdYo+yVuxPXoW0z7dnF0/KWqIPj2nzz3NRNE+HvNdPwi4hj7KdOg9XrzPPi4vXj+tj4U+9ij5Pf6rzj77Ul4/wSGFPsNL/z3yRc4+UF9eP8NPdz4oowI+VTbAPge9Yj8cAXo+rD4HPuG4wj4L2WE/HYB/PgUyET5qssc+zftfPy4dgj6B4Bo+VfLLPjhGXj8eCYM+sDEfPkunzT7QjV0/sGqDPpFlGz5Q6s0+8ppdP6NMhD5LvRE+g3jOPjnBXT8sUYU+09QEPsL6zj6N/V0/fi2GPtEL7z3fL88+9UheP7Cwhj5u+dg9BwDPPsSaXj9R6oY+/erIPQiYzj5u5l4/4veGPjotvz3qMc4+Qh5fPw/2hj466bs9gQTOPh00Xz+dUYU+TH7HPd/0yz4KxF8/2IWBPs7q4D0xRMc+3f9gP3QMez7OCPo9h2/CPoY0Yj/DT3c+KKMCPlU2wD4HvWI/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/wSGFPsNL/z3yRc4+UF9eP8EhhT7DS/898kXOPlBfXj/BIYU+w0v/PfJFzj5QX14/P7SFPnje/T3fFc8+hh9eP9FGhz4g3fk9qlHRPn1uXT9soYk+4p3zPSuq1D7TYFw/lY2MPpp36z2e0tg+2wlbPxPZjz5z2eE94YTdPk57WT/vV5M+/1bXPQGF4j7MxFc/m+SWPtmkzD13quc+w/FVP5Bamj42kMI91djsPiYMVD9PpJ0+LjO6PaDL8T7HJ1I/D66gPqWntD2vZfY+u09QP1yYoz7AzLE9JNn6PjZyTj8RmqY+vtCwPRp8/z5IbUw/+L6pPl3BsT0NNQI/RjNKP1oQrT4s77Q9UMgEP4DDRz91lLA+R5q6Pcp5Bz8tFkU/tlC0PqERwz0DSQo/xyNCP8FcuD5Bes099EANP1fbPj8B07w+HafYPQNtED/RKTs/xbTBPq+l5D16vhM/6A43P7O7xj7UY/E9SAkXP6K3Mj8Hwcs+75f+PZk7Gj83Oy4/PqLQPuH0BT7MRh0/DLIpP0FN1T7ikAw+qx8gPyAwJT/mwNk+9TETPnG4Ij9TxyA/g8fdPhEXGT42HiU/II8cP3V74T7S7B0+y2MnP4J4GD/mr+Q+XtwhPilnKT8qwBQ/lirnPkMnJT43/Co/rLkRP4bw6D6gqCc+EyIsP8Z4Dz8zCOo+S4cpPm/ILD8zGg4/RWfqPvMuKj4mAS0/SqENP0Vn6j7zLio+JgEtP0qhDT9FZ+o+8y4qPiYBLT9KoQ0/waoiPgvoMD7P8/I+ni9ZP7yrIj4x6zA+e/LyPsgvWT9jsiI+QAExPvTo8j4DMVk/TcQiPho9MT7SzvI+bTRZP9JyJD6KhDc+WMLvPl2lWT9HBSY+x1w+Ph0T7D7JNVo/wbInPrRYRz7QyuY+KApbPxH7KD4Tj08+V8vhPmLNWz/sAio+DcpXPq5Y3D5Ko1w/iFYqPlZqWz5sz9k+HwddP4SCKj7G0l0+1hTYPhtLXT98uyo+btdgPvf31T71ml0/ucoqPgXcYD7tDtY+Y5RdPyTXKj7iDmI+2ynVPnm3XT8q4So+X0FjPupC1D7J2l0/RQMrPn7kYz62+9M+tN9dP48bKz6DAWY+UnfSPg0YXj+ICSs+paNlPqyh0j70FF4/xIAqPqUVYz6mvdM+CwJeP8uyKD7yQlw+iVDWPgboXT9IuB4+QOE2Po0U5z6+Rlw/GN8JPj4t/D0mj/c+CCpbP3WS+j0x8LY9bdD9PuvrWj8Qqvw9k1S2Pfaw/z5WWFo/xNH/PUXBuz0zJAA/0QpaPzJOAj5JAcU9AWEAP6GvWT9USwY+zMnVPQGJAD/AMVk/yZwLPhm/7D0ZZQA/8bFYP8R/ET66gAM+KIL/PrxbWD/eDhc+0+4QPloo/T5bRlg/S+cbPiBPHT6A/Pk+DHFYP+2UHz6zeic+GqX2PlvBWD/X3iE+xF0uPu8A9D6SDlk/waoiPgvoMD7P8/I+ni9ZP1uNJT4f4jA+7+b3PqulVz/4oSY+0TY1Pk629T4o/1c//nEoPmaOPj4fCPA+NQBZPy5nKT6VvEc+kD/pPqdIWj8Kiik+++NLPg/U5T5q8Vo/1ewoPiaaSD7Srec+wKxaP7UZJz7VT0A+MBHsPsENWj/vIiQ+/1s1PoY78T7XXVk/KYsgPtYoKj4LvPU+wtZYPz01HT5m4iA+Cdb4PrmMWD+tmho+sBwaPpK7+j6BbVg/zvQYPtP6FT54u/s+bGRYPwpkGD5ZlhQ+Ewr8Pk1jWD/gzRo+zegYPhUR/D6fFVg/qLEfPmuQIj57O/s+BKpXP0LmIz4iYiw+4TP5PjyUVz9bjSU+H+IwPu/m9z6rpVc/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/waoiPgvoMD7P8/I+ni9ZP8GqIj4L6DA+z/PyPp4vWT/BqiI+C+gwPs/z8j6eL1k/2oQiPjBpMD68KvM+eShZPzoaIj4bBS8+5MXzPvgTWT/KayE+FNssPn2r9D5D91g/3HogPqgKKj4+vvU+YNhYP0pVHz5CuiY+9+j2Pju6WD/1Gx4+zRojPocn+D62mVg/3wAdPptuHz61ivk+TGxYP9U+HD4lCxw+sT77Pm4eWD/LMxw+qSwZPhyP/T6gklc/aT0dPq8rFz7aZgA/4qZWP+ZAHz7kCRY+f2oCPwVkVT/L2yE+CpwVPsaXBD/K8FM/QfskPlz/FT6X3gY/j1RSP4+eKD5RQhc+PDMJPzCUUD+DuSw+64kZPsqDCz+cuE4/ED0xPrwDHT71vA0/OcxMPxLsNT4BaSE+rtYPP5HaSj+nfTo+XlAmPoTMET878Eg/feI+PrewKz4zkhM/gxhHP3sPQz55gzE+VSoVP+RTRT/W6kY+CLs3PhWOFj9Hq0M/AF9KPpE7Pj74txc/mydCP/JfTT4x20Q+fp0YP3jWQD8kBVA+81lLPsssGT/ryj8/5xhSPrl9UT6KYhk/qRE/PzxnUz4qEFc+gx4ZPxTOPj/ZMlQ+yfVbPu+YGD+n0T4/St1UPjwUYD6AJRg/SNU+P7pfVT6FOWM+1ssXPxHYPj/1nlU+QV1lPq+OFz9M2z4/kLdVPrEZZj4EeRc/mdw+P5C3VT6xGWY+BHkXP5ncPj+Qt1U+sRlmPgR5Fz+Z3D4/Ei15vPEpNT0lX98+hgpmP30NebzbLTU9Ll7fPsEKZj/pMHi8JEk1Pd9W3z6BDGY/tNp1vBuTNT2KQt8+XhFmP4ymN7yLSj09y5fcPjSzZj+HrOm7PKVFPV022T7ee2c/yAv3uh+OUD2lS9Q+RZdoPwhEODuzhlo9vuDPPtKMaT/nyfM7QmVkPWUOyz6XkGo/9sMaPOG7aD0kycg+FghrPw9lMDzWnGs99zrHPuxYaz8fcUs81T1vPXRsxT5EtWs/aMVLPO9Ibz0/m8U+Z6trP3RfVjy1t3A9dcvEPtDUaz/H6mA8DiZyPaT5wz5l/ms/HdtmPIv0cj0E7MM+CQBsP+R+eTxIgXU9BKPCPjxAbD9oGnY8Xgp1Pa2swj7zPmw/mU1ePGHNcT0f4sI+vzhsPyNWHTwQJGk9gvPCPiBBbD/vCky8UQQ7PbsFzD4BfGo/k1pBvRJZ5TwMgtU+K0FoP3dai72KAYg8dbzaPjPEZj+Esou9s3SHPMMy3j5j72U/KVWIvT4ijzz2ot8+/5xlP/GUgr3tFZw8iyjhPgNJZT8qL3C9FoSzPMEQ4z4342Q/6ihTvb760jzx1+Q+uIdkP9CsMb0oIfY80czlPvddZD88gw+90LwMPat35T4tgWQ/FdTfvAWfHD2xAeQ+AeZkP9UXrLyebyk9HPXhPvdpZT+PVIm8FQQyPR8i4D6922U/Ei15vPEpNT0lX98+hgpmPyyOeLys/TQ9vK3tPt9tYj+rq0u8kag6PY0C6j4gYGM/o8jau/qCRj0YMeE+U5FlPy2IprrPxFE9V3DXPvrcZz/2K446WMVWPfG50j727Gg/eL9Zuoe4Uj0PdtQ+vYtoP/AyvLt0fEg9H4/YPjyhZz+8EEu8kOA6PbNW3T5shmY/SXyevJ/KLD0jeOE+pohlPyNmzrxZ7iA9nlnkPjLRZD8ut/G8DSAYPXgs5j6DWWQ/d7EDvXCtEj0qMOc+dxVkP7xbB71B0xA9HIXnPvP+Yz+Mvfe85YQWPUw46T7PkmM/9xLFvM/6Ij2FAew+gd5iP4LMkrw/YC89LnXtPsB9Yj8sjni8rP00Pbyt7T7fbWI/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/Ei15vPEpNT0lX98+hgpmPxItebzxKTU9JV/fPoYKZj8SLXm88Sk1PSVf3z6GCmY/zId9vNGhND0vF98+GhxmP97jhLxBIzM98FTePmNLZj8wg468z8owPTEr3T5kk2Y/VyabvL+zLT1qp9s+TfBmP3cSqrxVBCo9HN7ZPp1cZz+MMrq8xvUlPXTz1z5Zz2c/BfnJvKfjIT0QG9Y+XjxoPyx617zCVh49XZ7UPimTaD/pSuG86qgbPQL/0z4At2g/FFblvIxrGj1fuNQ+iYxoP/2147x+oBo9m33WPqEkaD8qt928HPgbPbeF2D5arGc/zfHSvCahHj09kto+zDFnP6tdw7zlqCI9FYTcPh+8Zj8Si668+0IoPeYp3j4GV2Y/XPyTvFSxLz2wT98+Dg9mPxzXaryaoDg9dLvfPknyZT98eSq8s49CPTUv3z60D2Y/tizQuz5ZTT0dkN0+Cm1mP+WkELtL7Fg9zCDbPpL4Zj898AE7hCRlPQkC2D6lqGc/DVzJO0bAcT3LVdQ+k3NoP7usJjypTH49oUDQPspPaT8J9mM8ofuEPVDoyz66NGo/676NPAREij3HAMc+KDJrP0BLpTzkvY49aErBPtFTbD8v97g8unCSPX+Suz5ebW0/8H3JPBZ8lT3/6rY+a0puPxQc1jxkxZc9QXSzPj3qbj/eNd48CVCZPbCfsT7wO28/oALhPCXVmT0B/rA+5ldvP6AC4Twl1Zk9Af6wPuZXbz+gAuE8JdWZPQH+sD7mV28/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQDbNI75b6EK/XaoOQDjNY77tIje/XaoOQAJNuL5nPR2/XaoOQGez/r7iVwO/XaoOQLRZD7/sJO++XaoOQLRZD7+HMPq+XaoOQLRZD7/Ckwy/XaoOQLRZD78TZCS/XaoOQLRZD79b6EK/XaoOQLRZD783p2W/XaoOQLRZD7+aYoO/XaoOQLRZD78wx4+/XaoOQLVZD79gp5S/XaoOQGaz/r5gp4y/XaoOQAFNuL6NG3a/XaoOQDbNY75a6FK/XaoOQDbNI75b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/XaoOQLRZD79b6EK/MeoOQLRZD7/9y0O/lZ4PQLRZD79LT0a/4LYQQLRZD7/kNkq/biISQLRZD79mR0+/ltATQLRZD79vRVW/srAVQLRZD7+e9Vu/HLIXQLRZD7+QHGO/LMQZQLRZD7/kfmq/PNYbQLRZD7844XG/ptcdQLRZD78rCHm/wrcfQLRZD79auH+/62UhQLRZD78y24K/eNEiQLRZD79yY4W/w+kjQLRZD78+V4e/Jp4kQLRZD7/mmIi/+90kQLVZD7+2Com/yJIjQLVZD7+2Com/zO4fQLVZD7+2Com/dk4aQLVZD7+2Com/Mg4TQLVZD7+2Com/cIoKQLVZD7+2Com/mx8BQLVZD7+2Com/Q1TuP7VZD7+1Com/4gzaP7VZD7+1Com/8SHGP7VZD7+1Com/SEyzP7VZD7+1Com/wkSiP7VZD7+1Com/OsSTP7VZD7+1Com/joOIP7VZD7+1Com/mDuBP7VZD7+1Com/Ykp9P7VZD7+1Com/Ykp9P7VZD7+1Com/Ykp9P7VZD7+1Com/hUnlsoVJ5bL0BDW/9AQ1P4VJ5bKFSeWy9AQ1v/QENT8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/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AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP5IMgT+SDIE/AACAP5QDhD+UA4Q/AACAP/SeiD/0nog/AACAP6OYjj+jmI4/AACAP5KqlT+SqpU/AACAP7GOnT+xjp0/AACAP/D+pT/w/qU/AACAPz61rj8+ta4/AACAP4xrtz+Ma7c/AACAP8zbvz/M278/AACAP+q/xz/qv8c/AACAP9rRzj/a0c4/AACAP4rL1D+Ky9Q/AACAP+pm2T/qZtk/AACAP+ld3D/pXdw/AACAP3xq3T98at0/AACAP8BZ2z98at0/AACAP+iJ1T98at0/AACAP4SOzD98at0/AACAPyH7wD98at0/AACAP05jsz98at0/AACAP5hapD98at0/AACAP4x0lD98at0/AACAP7pEhD98at0/AACAP1+9aD98at0/AACAP/KrSj98at0/AACAP018Lz98at0/AACAP4ZVGD98at0/AACAP7xeBj98at0/AACAPyB+9T58at0/AACAPy077T58at0/AACAPy077T58at0/AACAPy077T58at0/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utbk/6lwQQLRZD797gqw/6lwQQLRZD79IT58/6lwQQLRZD79IT5k/6lwQQGaz/r6utZ0/6lwQQABNuL5IT6k/6lwQQDjNY76utbk/6lwQQDXNI757gsw/6lwQQDfNY75IT98/6lwQQAFNuL6tte8/6lwQQGez/r5HT/s/6lwQQLRZD7+utf8/6lwQQLNZD79HT/s/6lwQQLRZD7+ste8/6lwQQLRZD79GT98/6lwQQLRZD796gsw/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/x1UQQLRZD78EhME/0yMQQLRZD7/oRsY/PJwPQLRZD78pAs0/MpQOQLRZD7+ZudQ/4+AMQLRZD78Jcdw/fVcKQLRZD79KLOM/MM0GQLRZD78t7+c/KhcCQLRZD7+Dvek/uef0P7RZD790D+c/YGTeP7RZD7+//t8/DoDDP7RZD7+cAdY/ghanP7RZD79Djso/ggOMP7RZD7/qGr8/mEVqP7VZD7/IHbU/TKBKP7VZD78SDa4/pM4+P7RZD78DX6s/lpVQP7RZD78DX6s/8AZ9P7RZD78DX6s/wGabP7RZD78DX6s/CEq4P7RZD78DX6s/toLOP7RZD78DX6s/LmbXP7RZD78DX6s/VszRP7RZD78DX6s/osnCP7RZD78DX6s/UQ6tP7RZD78DX6s/okqTP7RZD78EX6s/qF1wP7RZD78EX6s/StY8P7RZD78EX6s/qF8RP7RZD78EX6s/hLTmPrRZD78EX6s/IE3QPrRZD78EX6s/IE3QPrRZD78EX6s/IE3QPrRZD78EX6s/hUnlsoVJ5bL0BDW/9AQ1P4VJ5bKFSeWy9AQ1v/QENT8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/ViaAP1YmgD8AAIA/V5eAP1eXgD8AAIA/AlCBPwJQgT8AAIA/VU2CP1VNgj8AAIA/T4yDP0+Mgz8AAIA/7wmFP+8JhT8AAIA/NMOGPzTDhj8AAIA/G7WIPxu1iD8AAIA/pdyKP6Xcij8AAIA/zzaNP882jT8AAIA/mcCPP5nAjz8AAIA/AHeSPwB3kj8AAIA/A1eVPwNXlT8AAIA/o12YP6NdmD8AAIA/3IebP9yHmz8AAIA/rtKeP67Snj8AAIA/FjuiPxY7oj8AAIA/Fr6lPxa+pT8AAIA/qlipP6pYqT8AAIA/0QetP9EHrT8AAIA/i8iwP4vIsD8AAIA/1Ze0P9WXtD8AAIA/rnK4P65yuD8AAIA/F1a8PxdWvD8AAIA/Cz/APws/wD8AAIA/iyrEP4sqxD8AAIA/lhXIP5YVyD8AAIA/Kv3LPyr9yz8AAIA/Rd7PP0Xezz8AAIA/57XTP+e10z8AAIA/DYHXPw2B1z8AAIA/tzzbP7c82z8AAIA/4+XeP+Pl3j8AAIA/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tFkPvxnErL4gwuK9tFkPvxnErL6ACWC9tFkPvxnErL5AFw69tFkPvxnErL5AFw69tFkPvxnErL5AFw69tFkPvxnErL6FSeWyhUnlMvQENT/0BDU/hUnlsoVJ5TL0BDU/9AQ1PwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD9vNoA/bzaAPwAAgD851oA/OdaAPwAAgD8Z2oE/GdqBPwAAgD/KPIM/yjyDPwAAgD8I+YQ/CPmEPwAAgD+QCYc/kAmHPwAAgD8caYk/HGmJPwAAgD9nEow/ZxKMPwAAgD8tAI8/LQCPPwAAgD8pLZI/KS2SPwAAgD8XlJU/F5SVPwAAgD+zL5k/sy+ZPwAAgD+3+pw/t/qcPwAAgD/g76A/4O+gPwAAgD/oCaU/6AmlPwAAgD+MQ6k/jEOpPwAAgD+Gl60/hpetPwAAgD+TALI/kwCyPwAAgD9tebY/bXm2PwAAgD/R/Lo/0fy6PwAAgD95hb8/eYW/PwAAgD8hDsQ/IQ7EPwAAgD+Ekcg/hJHIPwAAgD9eCs0/XgrNPwAAgD9qc9E/anPRPwAAgD9mx9U/ZsfVPwAAgD8KAdo/CgHaPwAAgD8QG94/EBvePwAAgD86EOI/OhDiPwAAgD8/2+U/P9vlPwAAgD/Zduk/2XbpPwAAgD/I3ew/yN3sPwAAgD/FCvA/xQrwPwAAgD+L+PI/i/jyPwAAgD/VofU/1aH1PwAAgD9hAfg/YQH4PwAAgD/pEfo/6RH6PwAAgD8mzvs/Js77PwAAgD/YMP0/2DD9PwAAgD+4NP4/uDT+PwAAgD+D1P4/g9T+PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD+Wsfo/8Qr/PwAAgD/YxO4/8Qr/PwAAgD+y89w/8Qr/PwAAgD8c7cY/8Qr/PwAAgD8OYK4/8Qr/PwAAgD+F+5Q/8Qr/PwAAgD/v3Hg/8Qr/PwAAgD/Az0w/8Qr/PwAAgD9wLSk/8Qr/PwAAgD/2UxE/8Qr/PwAAgD9CoQg/8Qr/PwAAgD9CoQg/8Qr/PwAAgD9CoQg/8Qr/PweaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8A2zSO+UHMIvQeaF8A4zWO+FTNePAeaF8ACTbi+kvnuPQeaF8Bns/6+XhZhPgeaF8C0WQ+/MIuIPgeaF8C0WQ+/kkl6PgeaF8C0WQ+/+K86PgeaF8C0WQ+/jPmyPQeaF8C0WQ+/UHMIvQeaF8C0WQ+/brYtvgeaF8C0WQ+/aA6ZvgeaF8CzWQ+/znTKvgeaF8C0WQ+/N9vdvgeaF8Bms/6+Ntu9vgeaF8AATbi+oeluvgeaF8A2zWO+qznEvQeaF8A2zSO+UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvSVLF8C0WQ+/gVX5vDFiFsC0WQ+/Xbu2vLDkFMC0WQ+/HHwevCvYEsC0WQ+/MPLbOypCEMC0WQ+/mM7VPDMoDcC0WQ+/HolDPc+PCcC0WQ+/ZiiRPYZ+BcC0WQ+/tBXCPd35AMC0WQ+/AAPzPb0O+L+0WQ+/bDMRPiBZ7b+0WQ+/4lsnPvTd4b+0WQ+/JDY7Pkmo1b+0WQ+/dv1LPizDyL+0WQ+/Hu1YPq85u7+0WQ+/ZEBhPt8Wrb+0WQ+/iDJkPpRsnr+0WQ+/iDJkPs9nj7+0WQ+/iDJkPlk8gL+0WQ+/iDJkPvI7Yr+0WQ+/iDJkPvCARL+0WQ+/iDJkPkCvJ7+0WQ+/iDJkPnYuDL+0WQ+/iDJkPjjM5L60WQ+/iDJkPpB7tb60WQ+/iDJkPig6i760WQ+/iDJkPvCtTb60WQ+/iDJkPpBCEr60WQ+/iDJkPuCgy720WQ+/iDJkPmDrk720WQ+/iDJkPqChgL20WQ+/iDJkPqChgL20WQ+/iDJkPqChgL20WQ+/iDJkPoVJ5bKFSeUy9AQ1P/QENT+FSeWyhUnlMvQENT/0BDU/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP5IMgT+SDIE/AACAP5QDhD+UA4Q/AACAP/SeiD/0nog/AACAP6OYjj+jmI4/AACAP5KqlT+SqpU/AACAP7GOnT+xjp0/AACAP/D+pT/w/qU/AACAPz61rj8+ta4/AACAP4xrtz+Ma7c/AACAP8zbvz/M278/AACAP+q/xz/qv8c/AACAP9rRzj/a0c4/AACAP4rL1D+Ky9Q/AACAP+pm2T/qZtk/AACAP+ld3D/pXdw/AACAP3xq3T98at0/AACAP8BZ2z98at0/AACAP+iJ1T98at0/AACAP4SOzD98at0/AACAPyH7wD98at0/AACAP05jsz98at0/AACAP5hapD98at0/AACAP4x0lD98at0/AACAP7pEhD98at0/AACAP1+9aD98at0/AACAP/KrSj98at0/AACAP018Lz98at0/AACAP4ZVGD98at0/AACAP7xeBj98at0/AACAPyB+9T58at0/AACAPy077T58at0/AACAPy077T58at0/AACAPy077T58at0/B5oXwLRZD7++SjI/B5oXwLRZD7++SjI/B5oXwLRZD7++SjI/B5oXwLRZD7++SjI/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AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/ViaAP1YmgD8AAIA/V5eAP1eXgD8AAIA/AlCBPwJQgT8AAIA/VU2CP1VNgj8AAIA/T4yDP0+Mgz8AAIA/7wmFP+8JhT8AAIA/NMOGPzTDhj8AAIA/G7WIPxu1iD8AAIA/pdyKP6Xcij8AAIA/zzaNP882jT8AAIA/mcCPP5nAjz8AAIA/AHeSPwB3kj8AAIA/A1eVPwNXlT8AAIA/o12YP6NdmD8AAIA/3IebP9yHmz8AAIA/rtKeP67Snj8AAIA/FjuiPxY7oj8AAIA/Fr6lPxa+pT8AAIA/qlipP6pYqT8AAIA/0QetP9EHrT8AAIA/i8iwP4vIsD8AAIA/1Ze0P9WXtD8AAIA/rnK4P65yuD8AAIA/F1a8PxdWvD8AAIA/Cz/APws/wD8AAIA/iyrEP4sqxD8AAIA/lhXIP5YVyD8AAIA/Kv3LPyr9yz8AAIA/Rd7PP0Xezz8AAIA/57XTP+e10z8AAIA/DYHXPw2B1z8AAIA/tzzbP7c82z8AAIA/4+XeP+Pl3j8AAIA/kXniP5F54j8AAIA/vvTlP7705T8AAIA/aFTpP2hU6T8AAIA/lJXsP5SV7D8AAIA/OLXvPzi17z8AAIA/VrDyP1aw8j8AAIA/7oP1P+6D9T8AAIA//yz4P/8s+D8AAIA/hqj6P4ao+j8AAIA/gvP8P4Lz/D8AAIA/8Qr/P/EK/z8AAIA/+nUAQPp1AEAAAIA/E0oBQBNKAUAAAIA/pAACQKQAAkAAAIA/jJgCQIyYAkAAAIA/qBADQKgQA0AAAIA/2GcDQNhnA0AAAIA//ZwDQP2cA0AAAIA/9a4DQPWuA0AAAIA/9a4DQPWuA0AAAIA/9a4DQPWuA0AAAIA/9a4DQPWuA0AAAIA/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AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD+SDIE/kgyBPwAAgD+UA4Q/lAOEPwAAgD/0nog/9J6IPwAAgD+jmI4/o5iOPwAAgD+SqpU/kqqVPwAAgD+xjp0/sY6dPwAAgD/w/qU/8P6lPwAAgD8+ta4/PrWuPwAAgD+Ma7c/jGu3PwAAgD/M278/zNu/PwAAgD/qv8c/6r/HPwAAgD/a0c4/2tHOPwAAgD+Ky9Q/isvUPwAAgD/qZtk/6mbZPwAAgD/pXdw/6V3cPwAAgD98at0/fGrdPwAAgD/AWds/fGrdPwAAgD/oidU/fGrdPwAAgD+Ejsw/fGrdPwAAgD8h+8A/fGrdPwAAgD9OY7M/fGrdPwAAgD+YWqQ/fGrdPwAAgD+MdJQ/fGrdPwAAgD+6RIQ/fGrdPwAAgD9fvWg/fGrdPwAAgD/yq0o/fGrdPwAAgD9NfC8/fGrdPwAAgD+GVRg/fGrdPwAAgD+8XgY/fGrdPwAAgD8gfvU+fGrdPwAAgD8tO+0+fGrdPwAAgD8tO+0+fGrdPwAAgD8tO+0+fGrdPw=="}]} diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index ab04fae8e..54d207e5f 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -6,9 +6,9 @@ #include "SMaterialLayer.h" #include "coreutil.h" #include "CSkinnedMesh.h" -#include "ISkinnedMesh.h" -#include "irrTypes.h" +#include "IAnimatedMesh.h" #include "IReadFile.h" +#include "irrTypes.h" #include "matrix4.h" #include "path.h" #include "quaternion.h" @@ -23,9 +23,11 @@ #include #include #include +#include #include #include #include +#include namespace irr { @@ -51,6 +53,28 @@ core::vector3df convertHandedness(const core::vector3df &p) return core::vector3df(p.X, p.Y, -p.Z); } +template <> +core::quaternion convertHandedness(const core::quaternion &q) +{ + return core::quaternion(q.X, q.Y, -q.Z, q.W); +} + +template <> +core::matrix4 convertHandedness(const core::matrix4 &mat) +{ + // Base transformation between left & right handed coordinate systems. + static const core::matrix4 invertZ = core::matrix4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1); + // Convert from left-handed to right-handed, + // then apply mat, + // then convert from right-handed to left-handed. + // Both conversions just invert Z. + return invertZ * mat * invertZ; +} + namespace scene { using SelfType = CGLTFMeshFileLoader; @@ -196,6 +220,8 @@ ACCESSOR_PRIMITIVE(u16, UNSIGNED_SHORT) ACCESSOR_PRIMITIVE(u32, UNSIGNED_INT) ACCESSOR_TYPES(core::vector3df, VEC3, FLOAT) +ACCESSOR_TYPES(core::quaternion, VEC4, FLOAT) +ACCESSOR_TYPES(core::matrix4, MAT4, FLOAT) template T SelfType::Accessor::get(std::size_t i) const @@ -340,7 +366,7 @@ IAnimatedMesh* SelfType::createMesh(io::IReadFile* file) auto *mesh = new CSkinnedMesh(); MeshExtractor parser(std::move(model.value()), mesh); try { - parser.loadNodes(); + parser.load(); } catch (std::runtime_error &e) { os::Printer::log("glTF loader", e.what(), ELL_ERROR); mesh->drop(); @@ -397,61 +423,134 @@ static video::E_TEXTURE_CLAMP convertTextureWrap(const Wrap wrap) { } } -/** - * Load up the rawest form of the model. The vertex positions and indices. - * Documentation: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes - * If material is undefined, then a default material MUST be used. -*/ -void SelfType::MeshExtractor::loadMesh( - const std::size_t meshIdx, - ISkinnedMesh::SJoint *parent) const +void SelfType::MeshExtractor::addPrimitive( + const tiniergltf::MeshPrimitive &primitive, + const std::optional skinIdx, + CSkinnedMesh::SJoint *parent) { - for (std::size_t pi = 0; pi < getPrimitiveCount(meshIdx); ++pi) { - const auto &primitive = m_gltf_model.meshes->at(meshIdx).primitives.at(pi); - auto vertices = getVertices(primitive); - if (!vertices.has_value()) - continue; // "When positions are not specified, client implementations SHOULD skip primitive’s rendering" + auto vertices = getVertices(primitive); + if (!vertices.has_value()) + return; // "When positions are not specified, client implementations SHOULD skip primitive’s rendering" - // Excludes the max value for consistency. - if (vertices->size() >= std::numeric_limits::max()) - throw std::runtime_error("too many vertices"); + const auto n_vertices = vertices->size(); - // Apply the global transform along the parent chain. - transformVertices(*vertices, parent->GlobalMatrix); + // Excludes the max value for consistency. + if (n_vertices >= std::numeric_limits::max()) + throw std::runtime_error("too many vertices"); - auto maybeIndices = getIndices(primitive); - std::vector indices; - if (maybeIndices.has_value()) { - indices = std::move(*maybeIndices); - checkIndices(indices, vertices->size()); - } else { - // Non-indexed geometry - indices = generateIndices(vertices->size()); - } + // Apply the global transform along the parent chain. + transformVertices(*vertices, parent->GlobalMatrix); - m_irr_model->addMeshBuffer( - new SSkinMeshBuffer(std::move(*vertices), std::move(indices))); - auto *meshbuf = m_irr_model->getMeshBuffer(m_irr_model->getMeshBufferCount() - 1); - auto &irr_mat = meshbuf->getMaterial(); + auto maybeIndices = getIndices(primitive); + std::vector indices; + if (maybeIndices.has_value()) { + indices = std::move(*maybeIndices); + checkIndices(indices, vertices->size()); + } else { + // Non-indexed geometry + indices = generateIndices(vertices->size()); + } - if (primitive.material.has_value()) { - const auto &material = m_gltf_model.materials->at(*primitive.material); - if (material.pbrMetallicRoughness.has_value()) { - const auto &texture = material.pbrMetallicRoughness->baseColorTexture; - if (texture.has_value()) { - const auto meshbufNr = m_irr_model->getMeshBufferCount() - 1; - m_irr_model->setTextureSlot(meshbufNr, static_cast(texture->index)); - const auto samplerIdx = m_gltf_model.textures->at(texture->index).sampler; - if (samplerIdx.has_value()) { - auto &sampler = m_gltf_model.samplers->at(*samplerIdx); - auto &layer = irr_mat.TextureLayers[0]; - layer.TextureWrapU = convertTextureWrap(sampler.wrapS); - layer.TextureWrapV = convertTextureWrap(sampler.wrapT); - } + m_irr_model->addMeshBuffer( + new SSkinMeshBuffer(std::move(*vertices), std::move(indices))); + const auto meshbufNr = m_irr_model->getMeshBufferCount() - 1; + auto *meshbuf = m_irr_model->getMeshBuffer(meshbufNr); + + if (primitive.material.has_value()) { + const auto &material = m_gltf_model.materials->at(*primitive.material); + if (material.pbrMetallicRoughness.has_value()) { + const auto &texture = material.pbrMetallicRoughness->baseColorTexture; + if (texture.has_value()) { + m_irr_model->setTextureSlot(meshbufNr, static_cast(texture->index)); + const auto samplerIdx = m_gltf_model.textures->at(texture->index).sampler; + if (samplerIdx.has_value()) { + auto &sampler = m_gltf_model.samplers->at(*samplerIdx); + auto &layer = meshbuf->getMaterial().TextureLayers[0]; + layer.TextureWrapU = convertTextureWrap(sampler.wrapS); + layer.TextureWrapV = convertTextureWrap(sampler.wrapT); } } } } + + if (!skinIdx.has_value()) { + // No skin => all vertices belong entirely to their parent + for (std::size_t v = 0; v < n_vertices; ++v) { + auto *weight = m_irr_model->addWeight(parent); + weight->buffer_id = meshbufNr; + weight->vertex_id = v; + weight->strength = 1.0f; + } + return; + } + + const auto &skin = m_gltf_model.skins->at(*skinIdx); + + const auto &attrs = primitive.attributes; + const auto &joints = attrs.joints; + if (!joints.has_value()) + return; + + const auto &weights = attrs.weights; + for (std::size_t set = 0; set < joints->size(); ++set) { + const auto jointAccessor = ([&]() -> ArrayAccessorVariant<4, u8, u16> { + const auto idx = joints->at(set); + const auto &acc = m_gltf_model.accessors->at(idx); + + switch (acc.componentType) { + case tiniergltf::Accessor::ComponentType::UNSIGNED_BYTE: + return Accessor>::make(m_gltf_model, idx); + case tiniergltf::Accessor::ComponentType::UNSIGNED_SHORT: + return Accessor>::make(m_gltf_model, idx); + default: + throw std::runtime_error("invalid component type"); + } + })(); + + const auto weightAccessor = createNormalizedValuesAccessor<4>(m_gltf_model, weights->at(set)); + + for (std::size_t v = 0; v < n_vertices; ++v) { + std::array jointIdxs; + if (std::holds_alternative>>(jointAccessor)) { + const auto jointIdxsU8 = std::get>>(jointAccessor).get(v); + jointIdxs = {jointIdxsU8[0], jointIdxsU8[1], jointIdxsU8[2], jointIdxsU8[3]}; + } else if (std::holds_alternative>>(jointAccessor)) { + jointIdxs = std::get>>(jointAccessor).get(v); + } + std::array strengths = getNormalizedValues(weightAccessor, v); + + // 4 joints per set + for (std::size_t in_set = 0; in_set < 4; ++in_set) { + u16 jointIdx = jointIdxs[in_set]; + f32 strength = strengths[in_set]; + if (strength == 0) + continue; + + CSkinnedMesh::SWeight *weight = m_irr_model->addWeight(m_loaded_nodes.at(skin.joints.at(jointIdx))); + weight->buffer_id = meshbufNr; + weight->vertex_id = v; + weight->strength = strength; + } + } + } +} + +/** + * Load up the rawest form of the model. The vertex positions and indices. + * Documentation: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes + * If material is undefined, then a default material MUST be used. + */ +void SelfType::MeshExtractor::deferAddMesh( + const std::size_t meshIdx, + const std::optional skinIdx, + CSkinnedMesh::SJoint *parent) +{ + m_mesh_loaders.emplace_back([=] { + for (std::size_t pi = 0; pi < getPrimitiveCount(meshIdx); ++pi) { + const auto &primitive = m_gltf_model.meshes->at(meshIdx).primitives.at(pi); + addPrimitive(primitive, skinIdx, parent); + } + }); } // Base transformation between left & right handed coordinate systems. @@ -464,51 +563,75 @@ static const core::matrix4 leftToRight = core::matrix4( ); static const core::matrix4 rightToLeft = leftToRight; -static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m) +static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, CSkinnedMesh::SJoint *joint) { // Note: Under the hood, this casts these doubles to floats. - return core::matrix4( + core::matrix4 mat = convertHandedness(core::matrix4( m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], - m[12], m[13], m[14], m[15]); + m[12], m[13], m[14], m[15])); + + // Decompose the matrix into translation, scale, and rotation. + joint->Animatedposition = mat.getTranslation(); + + auto scale = mat.getScale(); + joint->Animatedscale = scale; + core::matrix4 inverseScale; + inverseScale.setScale(core::vector3df( + scale.X == 0 ? 0 : 1 / scale.X, + scale.Y == 0 ? 0 : 1 / scale.Y, + scale.Z == 0 ? 0 : 1 / scale.Z)); + + core::matrix4 axisNormalizedMat = inverseScale * mat; + joint->Animatedrotation = axisNormalizedMat.getRotationDegrees(); + // Invert the rotation because it is applied using `getMatrix_transposed`, + // which again inverts. + joint->Animatedrotation.makeInverse(); + + return mat; } -static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs) +static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, CSkinnedMesh::SJoint *joint) { const auto &trans = trs.translation; const auto &rot = trs.rotation; const auto &scale = trs.scale; core::matrix4 transMat; - transMat.setTranslation(core::vector3df(trans[0], trans[1], trans[2])); - core::matrix4 rotMat = core::quaternion(rot[0], rot[1], rot[2], rot[3]).getMatrix(); + joint->Animatedposition = convertHandedness(core::vector3df(trans[0], trans[1], trans[2])); + transMat.setTranslation(joint->Animatedposition); + core::matrix4 rotMat; + joint->Animatedrotation = convertHandedness(core::quaternion(rot[0], rot[1], rot[2], rot[3])); + core::quaternion(joint->Animatedrotation).getMatrix_transposed(rotMat); + joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]); core::matrix4 scaleMat; - scaleMat.setScale(core::vector3df(scale[0], scale[1], scale[2])); + scaleMat.setScale(joint->Animatedscale); return transMat * rotMat * scaleMat; } -static core::matrix4 loadTransform(std::optional> transform) { +static core::matrix4 loadTransform(std::optional> transform, + CSkinnedMesh::SJoint *joint) { if (!transform.has_value()) { return core::matrix4(); } - core::matrix4 mat = std::visit([](const auto &t) { return loadTransform(t); }, *transform); - return rightToLeft * mat * leftToRight; + return std::visit([joint](const auto &t) { return loadTransform(t, joint); }, *transform); } void SelfType::MeshExtractor::loadNode( const std::size_t nodeIdx, - ISkinnedMesh::SJoint *parent) const + CSkinnedMesh::SJoint *parent) { const auto &node = m_gltf_model.nodes->at(nodeIdx); auto *joint = m_irr_model->addJoint(parent); - const core::matrix4 transform = loadTransform(node.transform); + const core::matrix4 transform = loadTransform(node.transform, joint); joint->LocalMatrix = transform; joint->GlobalMatrix = parent ? parent->GlobalMatrix * joint->LocalMatrix : joint->LocalMatrix; if (node.name.has_value()) { joint->Name = node.name->c_str(); } + m_loaded_nodes[nodeIdx] = joint; if (node.mesh.has_value()) { - loadMesh(*node.mesh, joint); + deferAddMesh(*node.mesh, node.skin, joint); } if (node.children.has_value()) { for (const auto &child : *node.children) { @@ -517,8 +640,10 @@ void SelfType::MeshExtractor::loadNode( } } -void SelfType::MeshExtractor::loadNodes() const +void SelfType::MeshExtractor::loadNodes() { + m_loaded_nodes = std::vector(m_gltf_model.nodes->size()); + std::vector isChild(m_gltf_model.nodes->size()); for (const auto &node : *m_gltf_model.nodes) { if (!node.children.has_value()) @@ -536,6 +661,92 @@ void SelfType::MeshExtractor::loadNodes() const } } +void SelfType::MeshExtractor::loadSkins() +{ + if (!m_gltf_model.skins.has_value()) + return; + + for (const auto &skin : *m_gltf_model.skins) { + if (!skin.inverseBindMatrices.has_value()) + continue; + const auto accessor = Accessor::make(m_gltf_model, *skin.inverseBindMatrices); + if (accessor.getCount() < skin.joints.size()) + throw std::runtime_error("accessor contains too few matrices"); + for (std::size_t i = 0; i < skin.joints.size(); ++i) { + m_loaded_nodes.at(skin.joints[i])->GlobalInversedMatrix = convertHandedness(accessor.get(i)); + } + } +} + +void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) +{ + const auto &anim = m_gltf_model.animations->at(animIdx); + for (const auto &channel : anim.channels) { + + const auto &sampler = anim.samplers.at(channel.sampler); + if (sampler.interpolation != tiniergltf::AnimationSampler::Interpolation::LINEAR) + throw std::runtime_error("unsupported interpolation"); + + const auto inputAccessor = Accessor::make(m_gltf_model, sampler.input); + const auto n_frames = inputAccessor.getCount(); + + if (!channel.target.node.has_value()) + throw std::runtime_error("no animated node"); + + const auto &joint = m_loaded_nodes.at(*channel.target.node); + switch (channel.target.path) { + case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: { + const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); + for (std::size_t i = 0; i < n_frames; ++i) { + auto *key = m_irr_model->addPositionKey(joint); + key->frame = inputAccessor.get(i); + key->position = convertHandedness(outputAccessor.get(i)); + } + break; + } + case tiniergltf::AnimationChannelTarget::Path::ROTATION: { + const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); + for (std::size_t i = 0; i < n_frames; ++i) { + auto *key = m_irr_model->addRotationKey(joint); + key->frame = inputAccessor.get(i); + key->rotation = convertHandedness(outputAccessor.get(i)); + } + break; + } + case tiniergltf::AnimationChannelTarget::Path::SCALE: { + const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); + for (std::size_t i = 0; i < n_frames; ++i) { + auto *key = m_irr_model->addScaleKey(joint); + key->frame = inputAccessor.get(i); + key->scale = outputAccessor.get(i); + } + break; + } + case tiniergltf::AnimationChannelTarget::Path::WEIGHTS: + throw std::runtime_error("no support for morph animations"); + } + } +} + +void SelfType::MeshExtractor::load() +{ + loadNodes(); + for (const auto &load_mesh : m_mesh_loaders) { + load_mesh(); + } + loadSkins(); + // Load the first animation, if there is one. + if (m_gltf_model.animations.has_value()) { + if (m_gltf_model.animations->size() > 1) { + os::Printer::log("glTF loader", + "multiple animations are not supported", ELL_WARNING); + } + loadAnimation(0); + m_irr_model->setAnimationSpeed(1); + } + m_irr_model->finalize(); +} + /** * Extracts GLTF mesh indices. */ @@ -722,4 +933,3 @@ std::optional SelfType::tryParseGLTF(io::IReadFile* file) } // namespace scene } // namespace irr - diff --git a/irr/src/CGLTFMeshFileLoader.h b/irr/src/CGLTFMeshFileLoader.h index da306769e..7674fd46a 100644 --- a/irr/src/CGLTFMeshFileLoader.h +++ b/irr/src/CGLTFMeshFileLoader.h @@ -10,9 +10,11 @@ #include "path.h" #include "S3DVertex.h" -#include +#include "tiniergltf.hpp" +#include #include +#include #include namespace irr @@ -26,9 +28,9 @@ class CGLTFMeshFileLoader : public IMeshLoader public: CGLTFMeshFileLoader() noexcept {}; - bool isALoadableFileExtension(const io::path& filename) const override; + bool isALoadableFileExtension(const io::path &filename) const override; - IAnimatedMesh* createMesh(io::IReadFile* file) override; + IAnimatedMesh *createMesh(io::IReadFile *file) override; private: template @@ -94,7 +96,8 @@ private: const NormalizedValuesAccessor &accessor, const std::size_t i); - class MeshExtractor { + class MeshExtractor + { public: MeshExtractor(tiniergltf::GlTF &&model, CSkinnedMesh *mesh) noexcept @@ -114,12 +117,15 @@ private: std::size_t getPrimitiveCount(const std::size_t meshIdx) const; - void loadNodes() const; + void load(); private: const tiniergltf::GlTF m_gltf_model; CSkinnedMesh *m_irr_model; + std::vector> m_mesh_loaders; + std::vector m_loaded_nodes; + void copyPositions(const std::size_t accessorIdx, std::vector& vertices) const; @@ -129,16 +135,24 @@ private: void copyTCoords(const std::size_t accessorIdx, std::vector& vertices) const; - void loadMesh( - std::size_t meshIdx, - ISkinnedMesh::SJoint *parentJoint) const; + void addPrimitive(const tiniergltf::MeshPrimitive &primitive, + const std::optional skinIdx, + CSkinnedMesh::SJoint *parent); - void loadNode( - const std::size_t nodeIdx, - ISkinnedMesh::SJoint *parentJoint) const; + void deferAddMesh(const std::size_t meshIdx, + const std::optional skinIdx, + CSkinnedMesh::SJoint *parentJoint); + + void loadNode(const std::size_t nodeIdx, CSkinnedMesh::SJoint *parentJoint); + + void loadNodes(); + + void loadSkins(); + + void loadAnimation(const std::size_t animIdx); }; - std::optional tryParseGLTF(io::IReadFile* file); + std::optional tryParseGLTF(io::IReadFile *file); }; } // namespace scene diff --git a/src/unittest/test_irr_gltf_mesh_loader.cpp b/src/unittest/test_irr_gltf_mesh_loader.cpp index 99cb4b1b5..674f3c0dd 100644 --- a/src/unittest/test_irr_gltf_mesh_loader.cpp +++ b/src/unittest/test_irr_gltf_mesh_loader.cpp @@ -8,6 +8,7 @@ #include "irr_v2d.h" #include "irr_ptr.h" +#include "ISkinnedMesh.h" #include #include "catch.h" @@ -371,7 +372,91 @@ SECTION("simple sparse accessor") CHECK(vertices[i].Pos == expectedPositions[i]); } +// https://github.com/KhronosGroup/glTF-Sample-Models/tree/main/2.0/SimpleSkin +SECTION("simple skin") +{ + using ISkinnedMesh = irr::scene::ISkinnedMesh; + const auto mesh = loadMesh(model_stem + "simple_skin.gltf"); + REQUIRE(mesh != nullptr); + auto csm = dynamic_cast(mesh); + const auto joints = csm->getAllJoints(); + REQUIRE(joints.size() == 3); + + const auto findJoint = [&](const std::function &predicate) { + for (std::size_t i = 0; i < joints.size(); ++i) { + if (predicate(joints[i])) { + return joints[i]; + } + } + throw std::runtime_error("joint not found"); + }; + + // Check the node hierarchy + const auto parent = findJoint([](auto joint) { + return !joint->Children.empty(); + }); + REQUIRE(parent->Children.size() == 1); + const auto child = parent->Children[0]; + REQUIRE(child != parent); + + SECTION("transformations are correct") + { + CHECK(parent->Animatedposition == v3f(0, 0, 0)); + CHECK(parent->Animatedrotation == irr::core::quaternion()); + CHECK(parent->Animatedscale == v3f(1, 1, 1)); + CHECK(parent->GlobalInversedMatrix == irr::core::matrix4()); + const v3f childTranslation(0, 1, 0); + CHECK(child->Animatedposition == childTranslation); + CHECK(child->Animatedrotation == irr::core::quaternion()); + CHECK(child->Animatedscale == v3f(1, 1, 1)); + irr::core::matrix4 inverseBindMatrix; + inverseBindMatrix.setInverseTranslation(childTranslation); + CHECK(child->GlobalInversedMatrix == inverseBindMatrix); + } + + SECTION("weights are correct") + { + const auto weights = [&](const ISkinnedMesh::SJoint *joint) { + std::unordered_map weights; + for (std::size_t i = 0; i < joint->Weights.size(); ++i) { + const auto weight = joint->Weights[i]; + REQUIRE(weight.buffer_id == 0); + weights[weight.vertex_id] = weight.strength; + } + return weights; + }; + const auto parentWeights = weights(parent); + const auto childWeights = weights(child); + + const auto checkWeights = [&](irr::u32 index, irr::f32 parentWeight, irr::f32 childWeight) { + const auto getWeight = [](auto weights, auto index) { + const auto it = weights.find(index); + return it == weights.end() ? 0.0f : it->second; + }; + CHECK(getWeight(parentWeights, index) == parentWeight); + CHECK(getWeight(childWeights, index) == childWeight); + }; + checkWeights(0, 1.00, 0.00); + checkWeights(1, 1.00, 0.00); + checkWeights(2, 0.75, 0.25); + checkWeights(3, 0.75, 0.25); + checkWeights(4, 0.50, 0.50); + checkWeights(5, 0.50, 0.50); + checkWeights(6, 0.25, 0.75); + checkWeights(7, 0.25, 0.75); + checkWeights(8, 0.00, 1.00); + checkWeights(9, 0.00, 1.00); + } + + SECTION("there should be a third node not involved in skinning") + { + const auto other = findJoint([&](auto joint) { + return joint != child && joint != parent; + }); + CHECK(other->Weights.empty()); + } +} + driver->closeDevice(); driver->drop(); - } From 06907aa99b56ecd9c30e483a7cf3a77678293bfa Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Mon, 2 Sep 2024 21:11:08 +0200 Subject: [PATCH 180/200] Support floating-point animation frame numbers --- doc/lua_api.md | 3 +- games/devtest/mods/gltf/init.lua | 14 +++++++-- irr/include/IAnimatedMesh.h | 24 ++++------------ irr/include/IAnimatedMeshSceneNode.h | 8 +++--- irr/include/SAnimatedMesh.h | 21 +++++--------- irr/include/vector2d.h | 6 ++++ irr/src/CAnimatedMeshSceneNode.cpp | 43 ++++++++++++++-------------- irr/src/CAnimatedMeshSceneNode.h | 10 +++---- irr/src/CMeshManipulator.cpp | 2 +- irr/src/CSkinnedMesh.cpp | 12 ++++---- irr/src/CSkinnedMesh.h | 8 +++--- src/client/content_cao.cpp | 7 ++--- src/client/content_cao.h | 2 +- src/gui/guiScene.cpp | 2 +- src/gui/guiScene.h | 2 +- src/network/clientpackethandler.cpp | 15 ++++++---- src/network/networkprotocol.cpp | 1 + src/player.h | 2 +- src/remoteplayer.h | 4 +-- src/script/lua_api/l_object.cpp | 10 +++---- src/server.cpp | 16 ++++++++--- src/server.h | 4 +-- 22 files changed, 111 insertions(+), 105 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 0596c1e2f..a78afc847 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8059,8 +8059,7 @@ child will follow movement and rotation of that bone. * Animation interpolates towards the end frame but stops when it is reached * If looped, there is no interpolation back to the start frame * If looped, the model should look identical at start and end - * Only integer numbers are supported - * default: `{x=1, y=1}` + * default: `{x=1.0, y=1.0}` * `frame_speed`: How fast the animation plays, in frames per second (number) * default: `15.0` * `frame_blend`: number, default: `0.0` diff --git a/games/devtest/mods/gltf/init.lua b/games/devtest/mods/gltf/init.lua index 1a17ac05f..252fd017d 100644 --- a/games/devtest/mods/gltf/init.lua +++ b/games/devtest/mods/gltf/init.lua @@ -55,10 +55,20 @@ minetest.register_entity("gltf:simple_skin", { end }) --- Note: Model has an animation, but we can use it as a static test nevertheless -- The claws rendering incorrectly from one side is expected behavior: -- They use an unsupported double-sided material. -register_entity("frog", {"gltf_frog.png"}, false) +minetest.register_entity("gltf:frog", { + initial_properties = { + visual = "mesh", + mesh = "gltf_frog.gltf", + textures = {"gltf_frog.png"}, + backface_culling = false + }, + on_activate = function(self) + self.object:set_animation({x = 0, y = 0.75}, 1) + end +}) + minetest.register_node("gltf:frog", { description = "glTF frog, but it's a node", diff --git a/irr/include/IAnimatedMesh.h b/irr/include/IAnimatedMesh.h index 80b3bc3ca..2a1c1f4b1 100644 --- a/irr/include/IAnimatedMesh.h +++ b/irr/include/IAnimatedMesh.h @@ -19,11 +19,8 @@ irr::scene::SMeshBuffer etc. */ class IAnimatedMesh : public IMesh { public: - //! Gets the frame count of the animated mesh. - /** Note that the play-time is usually getFrameCount()-1 as it stops as soon as the last frame-key is reached. - \return The amount of frames. If the amount is 1, - it is a static, non animated mesh. */ - virtual u32 getFrameCount() const = 0; + //! Gets the maximum frame number, 0 if the mesh is static. + virtual f32 getMaxFrameNumber() const = 0; //! Gets the animation speed of the animated mesh. /** \return The number of frames per second to play the @@ -39,19 +36,10 @@ public: virtual void setAnimationSpeed(f32 fps) = 0; //! Returns the IMesh interface for a frame. - /** \param frame: Frame number as zero based index. The maximum - frame number is getFrameCount() - 1; - \param detailLevel: Level of detail. 0 is the lowest, 255 the - highest level of detail. Most meshes will ignore the detail level. - \param startFrameLoop: Because some animated meshes (.MD2) are - blended between 2 static frames, and maybe animated in a loop, - the startFrameLoop and the endFrameLoop have to be defined, to - prevent the animation to be blended between frames which are - outside of this loop. - If startFrameLoop and endFrameLoop are both -1, they are ignored. - \param endFrameLoop: see startFrameLoop. - \return Returns the animated mesh based on a detail level. */ - virtual IMesh *getMesh(s32 frame, s32 detailLevel = 255, s32 startFrameLoop = -1, s32 endFrameLoop = -1) = 0; + /** \param frame: Frame number, >= 0, <= getMaxFrameNumber() + Linear interpolation is used if this is between two frames. + \return Returns the animated mesh for the given frame */ + virtual IMesh *getMesh(f32 frame) = 0; //! Returns the type of the animated mesh. /** In most cases it is not necessary to use this method. diff --git a/irr/include/IAnimatedMeshSceneNode.h b/irr/include/IAnimatedMeshSceneNode.h index 65fdaaadf..8f9f6d661 100644 --- a/irr/include/IAnimatedMeshSceneNode.h +++ b/irr/include/IAnimatedMeshSceneNode.h @@ -63,7 +63,7 @@ public: virtual void setCurrentFrame(f32 frame) = 0; //! Sets the frame numbers between the animation is looped. - /** The default is 0 to getFrameCount()-1 of the mesh. + /** The default is 0 to getMaxFrameNumber() of the mesh. Number of played frames is end-start. It interpolates toward the last frame but stops when it is reached. It does not interpolate back to start even when looping. @@ -71,7 +71,7 @@ public: \param begin: Start frame number of the loop. \param end: End frame number of the loop. \return True if successful, false if not. */ - virtual bool setFrameLoop(s32 begin, s32 end) = 0; + virtual bool setFrameLoop(f32 begin, f32 end) = 0; //! Sets the speed with which the animation is played. /** \param framesPerSecond: Frames per second played. */ @@ -108,9 +108,9 @@ public: //! Returns the currently displayed frame number. virtual f32 getFrameNr() const = 0; //! Returns the current start frame number. - virtual s32 getStartFrame() const = 0; + virtual f32 getStartFrame() const = 0; //! Returns the current end frame number. - virtual s32 getEndFrame() const = 0; + virtual f32 getEndFrame() const = 0; //! Sets looping mode which is on by default. /** If set to false, animations will not be played looped. */ diff --git a/irr/include/SAnimatedMesh.h b/irr/include/SAnimatedMesh.h index 8fe14b41f..dd7306633 100644 --- a/irr/include/SAnimatedMesh.h +++ b/irr/include/SAnimatedMesh.h @@ -36,11 +36,9 @@ struct SAnimatedMesh final : public IAnimatedMesh mesh->drop(); } - //! Gets the frame count of the animated mesh. - /** \return Amount of frames. If the amount is 1, it is a static, non animated mesh. */ - u32 getFrameCount() const override + f32 getMaxFrameNumber() const override { - return static_cast(Meshes.size()); + return static_cast(Meshes.size() - 1); } //! Gets the default animation speed of the animated mesh. @@ -59,19 +57,14 @@ struct SAnimatedMesh final : public IAnimatedMesh } //! Returns the IMesh interface for a frame. - /** \param frame: Frame number as zero based index. The maximum frame number is - getFrameCount() - 1; - \param detailLevel: Level of detail. 0 is the lowest, - 255 the highest level of detail. Most meshes will ignore the detail level. - \param startFrameLoop: start frame - \param endFrameLoop: end frame - \return The animated mesh based on a detail level. */ - IMesh *getMesh(s32 frame, s32 detailLevel = 255, s32 startFrameLoop = -1, s32 endFrameLoop = -1) override + /** \param frame: Frame number as zero based index. + \return The animated mesh based for the given frame */ + IMesh *getMesh(f32 frame) override { if (Meshes.empty()) - return 0; + return nullptr; - return Meshes[frame]; + return Meshes[static_cast(frame)]; } //! adds a Mesh diff --git a/irr/include/vector2d.h b/irr/include/vector2d.h index caf69e6be..182965295 100644 --- a/irr/include/vector2d.h +++ b/irr/include/vector2d.h @@ -38,6 +38,12 @@ public: explicit constexpr vector2d(const std::array &arr) : X(arr[0]), Y(arr[1]) {} + template + constexpr static vector2d from(const vector2d &other) + { + return {static_cast(other.X), static_cast(other.Y)}; + } + // operators vector2d operator-() const { return vector2d(-X, -Y); } diff --git a/irr/src/CAnimatedMeshSceneNode.cpp b/irr/src/CAnimatedMeshSceneNode.cpp index 295d408f3..ba8bc3b78 100644 --- a/irr/src/CAnimatedMeshSceneNode.cpp +++ b/irr/src/CAnimatedMeshSceneNode.cpp @@ -16,6 +16,7 @@ #include "IAnimatedMesh.h" #include "IFileSystem.h" #include "quaternion.h" +#include namespace irr { @@ -80,7 +81,7 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs) } if (StartFrame == EndFrame) { - CurrentFrameNr = (f32)StartFrame; // Support for non animated meshes + CurrentFrameNr = StartFrame; // Support for non animated meshes } else if (Looping) { // play animation looped CurrentFrameNr += timeMs * FramesPerSecond; @@ -89,26 +90,26 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs) // the last frame must be identical to first one with our current solution. if (FramesPerSecond > 0.f) { // forwards... if (CurrentFrameNr > EndFrame) - CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, (f32)(EndFrame - StartFrame)); + CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame); } else // backwards... { if (CurrentFrameNr < StartFrame) - CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, (f32)(EndFrame - StartFrame)); + CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame); } } else { // play animation non looped CurrentFrameNr += timeMs * FramesPerSecond; if (FramesPerSecond > 0.f) { // forwards... - if (CurrentFrameNr > (f32)EndFrame) { - CurrentFrameNr = (f32)EndFrame; + if (CurrentFrameNr > EndFrame) { + CurrentFrameNr = EndFrame; if (LoopCallBack) LoopCallBack->OnAnimationEnd(this); } } else // backwards... { - if (CurrentFrameNr < (f32)StartFrame) { - CurrentFrameNr = (f32)StartFrame; + if (CurrentFrameNr < StartFrame) { + CurrentFrameNr = StartFrame; if (LoopCallBack) LoopCallBack->OnAnimationEnd(this); } @@ -159,9 +160,7 @@ void CAnimatedMeshSceneNode::OnRegisterSceneNode() IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame() { if (Mesh->getMeshType() != EAMT_SKINNED) { - s32 frameNr = (s32)getFrameNr(); - s32 frameBlend = (s32)(core::fract(getFrameNr()) * 1000.f); - return Mesh->getMesh(frameNr, frameBlend, StartFrame, EndFrame); + return Mesh->getMesh(getFrameNr()); } else { // As multiple scene nodes may be sharing the same skinned mesh, we have to // re-animate it every frame to ensure that this node gets the mesh that it needs. @@ -331,33 +330,33 @@ void CAnimatedMeshSceneNode::render() } //! Returns the current start frame number. -s32 CAnimatedMeshSceneNode::getStartFrame() const +f32 CAnimatedMeshSceneNode::getStartFrame() const { return StartFrame; } //! Returns the current start frame number. -s32 CAnimatedMeshSceneNode::getEndFrame() const +f32 CAnimatedMeshSceneNode::getEndFrame() const { return EndFrame; } //! sets the frames between the animation is looped. //! the default is 0 - MaximalFrameCount of the mesh. -bool CAnimatedMeshSceneNode::setFrameLoop(s32 begin, s32 end) +bool CAnimatedMeshSceneNode::setFrameLoop(f32 begin, f32 end) { - const s32 maxFrameCount = Mesh->getFrameCount() - 1; + const f32 maxFrame = Mesh->getMaxFrameNumber(); if (end < begin) { - StartFrame = core::s32_clamp(end, 0, maxFrameCount); - EndFrame = core::s32_clamp(begin, StartFrame, maxFrameCount); + StartFrame = std::clamp(end, 0, maxFrame); + EndFrame = std::clamp(begin, StartFrame, maxFrame); } else { - StartFrame = core::s32_clamp(begin, 0, maxFrameCount); - EndFrame = core::s32_clamp(end, StartFrame, maxFrameCount); + StartFrame = std::clamp(begin, 0, maxFrame); + EndFrame = std::clamp(end, StartFrame, maxFrame); } if (FramesPerSecond < 0) - setCurrentFrame((f32)EndFrame); + setCurrentFrame(EndFrame); else - setCurrentFrame((f32)StartFrame); + setCurrentFrame(StartFrame); return true; } @@ -532,7 +531,7 @@ void CAnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh) // get materials and bounding box Box = Mesh->getBoundingBox(); - IMesh *m = Mesh->getMesh(0, 0); + IMesh *m = Mesh->getMesh(0); if (m) { Materials.clear(); Materials.reallocate(m->getMeshBufferCount()); @@ -554,7 +553,7 @@ void CAnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh) // get start and begin time setAnimationSpeed(Mesh->getAnimationSpeed()); // NOTE: This had been commented out (but not removed!) in r3526. Which caused meshloader-values for speed to be ignored unless users specified explicitly. Missing a test-case where this could go wrong so I put the code back in. - setFrameLoop(0, Mesh->getFrameCount() - 1); + setFrameLoop(0, Mesh->getMaxFrameNumber()); } //! updates the absolute position based on the relative and the parents position diff --git a/irr/src/CAnimatedMeshSceneNode.h b/irr/src/CAnimatedMeshSceneNode.h index 0364ab527..e45edca86 100644 --- a/irr/src/CAnimatedMeshSceneNode.h +++ b/irr/src/CAnimatedMeshSceneNode.h @@ -45,7 +45,7 @@ public: //! sets the frames between the animation is looped. //! the default is 0 - MaximalFrameCount of the mesh. //! NOTE: setMesh will also change this value and set it to the full range of animations of the mesh - bool setFrameLoop(s32 begin, s32 end) override; + bool setFrameLoop(f32 begin, f32 end) override; //! Sets looping mode which is on by default. If set to false, //! animations will not be looped. @@ -93,9 +93,9 @@ public: //! Returns the current displayed frame number. f32 getFrameNr() const override; //! Returns the current start frame number. - s32 getStartFrame() const override; + f32 getStartFrame() const override; //! Returns the current end frame number. - s32 getEndFrame() const override; + f32 getEndFrame() const override; //! Sets if the scene node should not copy the materials of the mesh but use them in a read only style. /* In this way it is possible to change the materials a mesh causing all mesh scene nodes @@ -148,8 +148,8 @@ private: core::aabbox3d Box; IAnimatedMesh *Mesh; - s32 StartFrame; - s32 EndFrame; + f32 StartFrame; + f32 EndFrame; f32 FramesPerSecond; f32 CurrentFrameNr; diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index 2c9d05336..67b22a07e 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -193,7 +193,7 @@ s32 CMeshManipulator::getPolyCount(scene::IMesh *mesh) const //! Returns amount of polygons in mesh. s32 CMeshManipulator::getPolyCount(scene::IAnimatedMesh *mesh) const { - if (mesh && mesh->getFrameCount() != 0) + if (mesh && mesh->getMaxFrameNumber() != 0) return getPolyCount(mesh->getMesh(0)); return 0; diff --git a/irr/src/CSkinnedMesh.cpp b/irr/src/CSkinnedMesh.cpp index 56ef3efe1..875fd8e7e 100644 --- a/irr/src/CSkinnedMesh.cpp +++ b/irr/src/CSkinnedMesh.cpp @@ -111,11 +111,9 @@ CSkinnedMesh::~CSkinnedMesh() } } -//! returns the amount of frames in milliseconds. -//! If the amount is 1, it is a static (=non animated) mesh. -u32 CSkinnedMesh::getFrameCount() const +f32 CSkinnedMesh::getMaxFrameNumber() const { - return core::floor32(EndFrame + 1.f); + return EndFrame; } //! Gets the default animation speed of the animated mesh. @@ -133,14 +131,14 @@ void CSkinnedMesh::setAnimationSpeed(f32 fps) FramesPerSecond = fps; } -//! returns the animated mesh based on a detail level. 0 is the lowest, 255 the highest detail. Note, that some Meshes will ignore the detail level. -IMesh *CSkinnedMesh::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop) +//! returns the animated mesh based +IMesh *CSkinnedMesh::getMesh(f32 frame) { // animate(frame,startFrameLoop, endFrameLoop); if (frame == -1) return this; - animateMesh((f32)frame, 1.0f); + animateMesh(frame, 1.0f); skinMesh(); return this; } diff --git a/irr/src/CSkinnedMesh.h b/irr/src/CSkinnedMesh.h index 4b4c5e3b7..1be6ee7bc 100644 --- a/irr/src/CSkinnedMesh.h +++ b/irr/src/CSkinnedMesh.h @@ -27,8 +27,8 @@ public: //! destructor virtual ~CSkinnedMesh(); - //! returns the amount of frames. If the amount is 1, it is a static (=non animated) mesh. - u32 getFrameCount() const override; + //! If the duration is 0, it is a static (=non animated) mesh. + f32 getMaxFrameNumber() const override; //! Gets the default animation speed of the animated mesh. /** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */ @@ -39,8 +39,8 @@ public: The actual speed is set in the scene node the mesh is instantiated in.*/ void setAnimationSpeed(f32 fps) override; - //! returns the animated mesh based on a detail level (which is ignored) - IMesh *getMesh(s32 frame, s32 detailLevel = 255, s32 startFrameLoop = -1, s32 endFrameLoop = -1) override; + //! returns the animated mesh for the given frame + IMesh *getMesh(f32) override; //! Animates this mesh's joints based on frame input //! blend: {0-old position, 1-New position} diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index adec70983..c8acb3875 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1052,7 +1052,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) walking = true; } - v2s32 new_anim = v2s32(0,0); + v2f new_anim(0,0); bool allow_update = false; // increase speed if using fast or flying fast @@ -1799,10 +1799,9 @@ void GenericCAO::processMessage(const std::string &data) phys.speed_walk = override_speed_walk; } } else if (cmd == AO_CMD_SET_ANIMATION) { - // TODO: change frames send as v2s32 value v2f range = readV2F32(is); if (!m_is_local_player) { - m_animation_range = v2s32((s32)range.X, (s32)range.Y); + m_animation_range = range; m_animation_speed = readF32(is); m_animation_blend = readF32(is); // these are sent inverted so we get true when the server sends nothing @@ -1812,7 +1811,7 @@ void GenericCAO::processMessage(const std::string &data) LocalPlayer *player = m_env->getLocalPlayer(); if(player->last_animation == LocalPlayerAnimation::NO_ANIM) { - m_animation_range = v2s32((s32)range.X, (s32)range.Y); + m_animation_range = range; m_animation_speed = readF32(is); m_animation_blend = readF32(is); // these are sent inverted so we get true when the server sends nothing diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 3fdf01bc7..d138e39c3 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -99,7 +99,7 @@ private: v2s16 m_tx_basepos; bool m_initial_tx_basepos_set = false; bool m_tx_select_horiz_by_yawpitch = false; - v2s32 m_animation_range; + v2f m_animation_range; float m_animation_speed = 15.0f; float m_animation_blend = 0.0f; bool m_animation_loop = true; diff --git a/src/gui/guiScene.cpp b/src/gui/guiScene.cpp index 33310fe35..06784cd6e 100644 --- a/src/gui/guiScene.cpp +++ b/src/gui/guiScene.cpp @@ -157,7 +157,7 @@ void GUIScene::setStyles(const std::array &sty /** * Sets the frame loop range for the mesh */ -void GUIScene::setFrameLoop(s32 begin, s32 end) +void GUIScene::setFrameLoop(f32 begin, f32 end) { if (m_mesh->getStartFrame() != begin || m_mesh->getEndFrame() != end) m_mesh->setFrameLoop(begin, end); diff --git a/src/gui/guiScene.h b/src/gui/guiScene.h index 0f5f3a891..0634669f7 100644 --- a/src/gui/guiScene.h +++ b/src/gui/guiScene.h @@ -36,7 +36,7 @@ public: scene::IAnimatedMeshSceneNode *setMesh(scene::IAnimatedMesh *mesh = nullptr); void setTexture(u32 idx, video::ITexture *texture); void setBackgroundColor(const video::SColor &color) noexcept { m_bgcolor = color; }; - void setFrameLoop(s32 begin, s32 end); + void setFrameLoop(f32 begin, f32 end); void setAnimationSpeed(f32 speed); void enableMouseControl(bool enable) noexcept { m_mouse_ctrl = enable; }; void setRotation(v2f rot) noexcept { m_custom_rot = rot; }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 725b6a5c7..373e39b4e 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" +#include "irr_v2d.h" #include "util/base64.h" #include "client/camera.h" #include "client/mesh_generator_thread.h" @@ -1516,11 +1517,15 @@ void Client::handleCommand_LocalPlayerAnimations(NetworkPacket* pkt) LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); - *pkt >> player->local_animations[0]; - *pkt >> player->local_animations[1]; - *pkt >> player->local_animations[2]; - *pkt >> player->local_animations[3]; - *pkt >> player->local_animation_speed; + for (int i = 0; i < 4; ++i) { + if (getProtoVersion() >= 46) { + *pkt >> player->local_animations[i]; + } else { + v2s32 local_animation; + *pkt >> local_animation; + player->local_animations[i] = v2f::from(local_animation); + } + } player->last_animation = LocalPlayerAnimation::NO_ANIM; } diff --git a/src/network/networkprotocol.cpp b/src/network/networkprotocol.cpp index 38b958d24..40f8acef1 100644 --- a/src/network/networkprotocol.cpp +++ b/src/network/networkprotocol.cpp @@ -57,6 +57,7 @@ old servers. Rename TOCLIENT_DEATHSCREEN to TOCLIENT_DEATHSCREEN_LEGACY Rename TOSERVER_RESPAWN to TOSERVER_RESPAWN_LEGACY + Support float animation frame numbers in TOCLIENT_LOCAL_PLAYER_ANIMATIONS [scheduled bump for 5.10.0] */ diff --git a/src/player.h b/src/player.h index 972a04e02..c729f98a0 100644 --- a/src/player.h +++ b/src/player.h @@ -203,7 +203,7 @@ public: f32 movement_liquid_sink; f32 movement_gravity; - v2s32 local_animations[4]; + v2f local_animations[4]; float local_animation_speed; std::string inventory_formspec; diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 4923c307d..cbfc80d91 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -113,14 +113,14 @@ public: inline void setModified(const bool x) { m_dirty = x; } - void setLocalAnimations(v2s32 frames[4], float frame_speed) + void setLocalAnimations(v2f frames[4], float frame_speed) { for (int i = 0; i < 4; i++) local_animations[i] = frames[i]; local_animation_speed = frame_speed; } - void getLocalAnimations(v2s32 *frames, float *frame_speed) + void getLocalAnimations(v2f *frames, float *frame_speed) { for (int i = 0; i < 4; i++) frames[i] = local_animations[i]; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index b9ea0a4e4..ae863502f 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -433,10 +433,10 @@ int ObjectRef::l_set_local_animation(lua_State *L) if (player == nullptr) return 0; - v2s32 frames[4]; + v2f frames[4]; for (int i=0;i<4;i++) { if (!lua_isnil(L, 2+1)) - frames[i] = read_v2s32(L, 2+i); + frames[i] = read_v2f(L, 2+i); } float frame_speed = readParam(L, 6, 30.0f); @@ -453,12 +453,12 @@ int ObjectRef::l_get_local_animation(lua_State *L) if (player == nullptr) return 0; - v2s32 frames[4]; + v2f frames[4]; float frame_speed; player->getLocalAnimations(frames, &frame_speed); - for (const v2s32 &frame : frames) { - push_v2s32(L, frame); + for (const v2f &frame : frames) { + push_v2f(L, frame); } lua_pushnumber(L, frame_speed); diff --git a/src/server.cpp b/src/server.cpp index 7634e2433..405af63ef 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "irr_v2d.h" #include "network/connection.h" #include "network/networkprotocol.h" #include "network/serveropcodes.h" @@ -1987,14 +1988,21 @@ void Server::SendPlayerFov(session_t peer_id) Send(&pkt); } -void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4], +void Server::SendLocalPlayerAnimations(session_t peer_id, v2f animation_frames[4], f32 animation_speed) { NetworkPacket pkt(TOCLIENT_LOCAL_PLAYER_ANIMATIONS, 0, peer_id); - pkt << animation_frames[0] << animation_frames[1] << animation_frames[2] - << animation_frames[3] << animation_speed; + for (int i = 0; i < 4; ++i) { + if (m_clients.getProtocolVersion(peer_id) >= 46) { + pkt << animation_frames[i]; + } else { + pkt << v2s32::from(animation_frames[i]); + } + } + + pkt << animation_speed; Send(&pkt); } @@ -3424,7 +3432,7 @@ Address Server::getPeerAddress(session_t peer_id) } void Server::setLocalPlayerAnimations(RemotePlayer *player, - v2s32 animation_frames[4], f32 frame_speed) + v2f animation_frames[4], f32 frame_speed) { sanity_check(player); player->setLocalAnimations(animation_frames, frame_speed); diff --git a/src/server.h b/src/server.h index 57b543c11..0318ec6f6 100644 --- a/src/server.h +++ b/src/server.h @@ -344,7 +344,7 @@ public: Address getPeerAddress(session_t peer_id); - void setLocalPlayerAnimations(RemotePlayer *player, v2s32 animation_frames[4], + void setLocalPlayerAnimations(RemotePlayer *player, v2f animation_frames[4], f32 frame_speed); void setPlayerEyeOffset(RemotePlayer *player, v3f first, v3f third, v3f third_front); @@ -501,7 +501,7 @@ private: virtual void SendChatMessage(session_t peer_id, const ChatMessage &message); void SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed); - void SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4], + void SendLocalPlayerAnimations(session_t peer_id, v2f animation_frames[4], f32 animation_speed); void SendEyeOffset(session_t peer_id, v3f first, v3f third, v3f third_front); void SendPlayerPrivileges(session_t peer_id); From f1a436619f1581a8a0dcc7e00e85f978d6e1a4a7 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 14 May 2024 22:24:05 +0200 Subject: [PATCH 181/200] Add generic IPC mechanism between Lua envs --- doc/lua_api.md | 51 ++++++++++---- .../mods/unittests/inside_mapgen_env.lua | 5 +- games/devtest/mods/unittests/misc.lua | 25 +++++++ src/gamedef.h | 9 ++- src/script/cpp_api/s_async.cpp | 3 +- src/script/lua_api/CMakeLists.txt | 1 + src/script/lua_api/l_ipc.cpp | 68 +++++++++++++++++++ src/script/lua_api/l_ipc.h | 15 ++++ src/script/scripting_emerge.cpp | 2 + src/script/scripting_server.cpp | 3 + src/server.cpp | 9 +++ src/server.h | 19 ++++++ 12 files changed, 191 insertions(+), 19 deletions(-) create mode 100644 src/script/lua_api/l_ipc.cpp create mode 100644 src/script/lua_api/l_ipc.h diff --git a/doc/lua_api.md b/doc/lua_api.md index a78afc847..eb3c111b9 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6855,17 +6855,6 @@ This allows you easy interoperability for delegating work to jobs. * Register a path to a Lua file to be imported when an async environment is initialized. You can use this to preload code which you can then call later using `minetest.handle_async()`. -* `minetest.register_portable_metatable(name, mt)`: - * Register a metatable that should be preserved when data is transferred - between the main thread and the async environment. - * `name` is a string that identifies the metatable. It is recommended to - follow the `modname:name` convention for this identifier. - * `mt` is the metatable to register. - * Note that it is allowed to register the same metatable under multiple - names, but it is not allowed to register multiple metatables under the - same name. - * You must register the metatable in both the main environment - and the async environment for this mechanism to work. ### List of APIs available in an async environment @@ -6895,7 +6884,8 @@ Functions: * Standalone helpers such as logging, filesystem, encoding, hashing or compression APIs -* `minetest.register_portable_metatable` (see above) +* `minetest.register_portable_metatable` +* IPC Variables: @@ -6973,6 +6963,7 @@ Functions: * `minetest.get_node`, `set_node`, `find_node_near`, `find_nodes_in_area`, `spawn_tree` and similar * these only operate on the current chunk (if inside a callback) +* IPC Variables: @@ -7050,6 +7041,31 @@ Server this can make transfer of bigger files painless (if set up). Nevertheless it is advised not to use dynamic media for big media files. +IPC +--- + +The engine provides a generalized mechanism to enable sharing data between the +different Lua environments (main, mapgen and async). +It is essentially a shared in-memory key-value store. + +* `minetest.ipc_get(key)`: + * Read a value from the shared data area. + * `key`: string, should use the `"modname:thing"` convention to avoid conflicts. + * returns an arbitrary Lua value, or `nil` if this key does not exist +* `minetest.ipc_set(key, value)`: + * Write a value to the shared data area. + * `key`: as above + * `value`: an arbitrary Lua value, cannot be or contain userdata. + +Interacting with the shared data will perform an operation comparable to +(de)serialization on each access. +For that reason modifying references will not have any effect, as in this example: +```lua +minetest.ipc_set("test:foo", {}) +minetest.ipc_get("test:foo").subkey = "value" -- WRONG! +minetest.ipc_get("test:foo") -- returns an empty table +``` + Bans ---- @@ -7449,6 +7465,17 @@ Misc. * `minetest.global_exists(name)` * Checks if a global variable has been set, without triggering a warning. +* `minetest.register_portable_metatable(name, mt)`: + * Register a metatable that should be preserved when Lua data is transferred + between environments (via IPC or `handle_async`). + * `name` is a string that identifies the metatable. It is recommended to + follow the `modname:name` convention for this identifier. + * `mt` is the metatable to register. + * Note that the same metatable can be registered under multiple names, + but multiple metatables must not be registered under the same name. + * You must register the metatable in both the main environment + and the async environment for this mechanism to work. + Global objects -------------- diff --git a/games/devtest/mods/unittests/inside_mapgen_env.lua b/games/devtest/mods/unittests/inside_mapgen_env.lua index a8df004de..021a4a44c 100644 --- a/games/devtest/mods/unittests/inside_mapgen_env.lua +++ b/games/devtest/mods/unittests/inside_mapgen_env.lua @@ -22,9 +22,8 @@ local function do_tests() assert(core.registered_items["unittests:description_test"].on_place == true) end --- there's no (usable) communcation path between mapgen and the regular env --- so we just run the test unconditionally -do_tests() +-- this is checked from the main env +core.ipc_set("unittests:mg", { pcall(do_tests) }) core.register_on_generated(function(vm, pos1, pos2, blockseed) local n = tonumber(core.get_mapgen_setting("chunksize")) * 16 - 1 diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index 6ff5c7e84..1b39708d9 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -254,3 +254,28 @@ local function test_gennotify_api() assert(#custom == 0, "custom ids not empty") end unittests.register("test_gennotify_api", test_gennotify_api) + +-- <=> inside_mapgen_env.lua +local function test_mapgen_env(cb) + -- emerge threads start delayed so this can take a second + local res = core.ipc_get("unittests:mg") + if res == nil then + return core.after(0, test_mapgen_env, cb) + end + -- handle error status + if res[1] then + cb() + else + cb(res[2]) + end +end +unittests.register("test_mapgen_env", test_mapgen_env, {async=true}) + +local function test_ipc_vector_preserve(cb) + -- the IPC also uses register_portable_metatable + core.ipc_set("unittests:v", vector.new(4, 0, 4)) + local v = core.ipc_get("unittests:v") + assert(type(v) == "table") + assert(vector.check(v)) +end +unittests.register("test_ipc_vector_preserve", test_ipc_vector_preserve) diff --git a/src/gamedef.h b/src/gamedef.h index 9a6c55ab1..f8d6d79e7 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -34,19 +34,19 @@ class Camera; class ModChannel; class ModStorage; class ModStorageDatabase; +struct SubgameSpec; +struct ModSpec; +struct ModIPCStore; namespace irr::scene { class IAnimatedMesh; class ISceneManager; } -struct SubgameSpec; -struct ModSpec; /* An interface for fetching game-global definitions like tool and mapnode properties */ - class IGameDef { public: @@ -63,6 +63,9 @@ public: // environment thread. virtual IRollbackManager* getRollbackManager() { return NULL; } + // Only usable on server. + virtual ModIPCStore *getModIPCStore() { return nullptr; } + // Shorthands // TODO: these should be made const-safe so that a const IGameDef* is // actually usable diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index 75b1a8205..bfcfb4f7d 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -50,11 +50,12 @@ AsyncEngine::~AsyncEngine() } // Wait for threads to finish + infostream << "AsyncEngine: Waiting for " << workerThreads.size() + << " threads" << std::endl; for (AsyncWorkerThread *workerThread : workerThreads) { workerThread->wait(); } - // Force kill all threads for (AsyncWorkerThread *workerThread : workerThreads) { delete workerThread; } diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index d9405e4fe..2e12f8c56 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -6,6 +6,7 @@ set(common_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_env.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_http.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_inventory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/l_ipc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_item.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_itemstackmeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_mapgen.cpp diff --git a/src/script/lua_api/l_ipc.cpp b/src/script/lua_api/l_ipc.cpp new file mode 100644 index 000000000..eb1eaedd7 --- /dev/null +++ b/src/script/lua_api/l_ipc.cpp @@ -0,0 +1,68 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "lua_api/l_ipc.h" +#include "lua_api/l_internal.h" +#include "common/c_packer.h" +#include "server.h" +#include "debug.h" + +typedef std::shared_lock SharedReadLock; +typedef std::unique_lock SharedWriteLock; + +int ModApiIPC::l_ipc_get(lua_State *L) +{ + auto *store = getGameDef(L)->getModIPCStore(); + + auto key = readParam(L, 1); + + { + SharedReadLock autolock(store->mutex); + auto it = store->map.find(key); + if (it == store->map.end()) + lua_pushnil(L); + else + script_unpack(L, it->second.get()); + } + return 1; +} + +int ModApiIPC::l_ipc_set(lua_State *L) +{ + auto *store = getGameDef(L)->getModIPCStore(); + + auto key = readParam(L, 1); + + luaL_checkany(L, 2); + std::unique_ptr pv; + if (!lua_isnil(L, 2)) { + pv.reset(script_pack(L, 2)); + if (pv->contains_userdata) + throw LuaError("Userdata not allowed"); + } + + { + SharedWriteLock autolock(store->mutex); + if (pv) + store->map[key] = std::move(pv); + else + store->map.erase(key); // delete the map value for nil + } + return 0; +} + +/* + * Implementation note: + * Iterating over the IPC table is intentionally not supported. + * Mods should know what they have set. + * This has the nice side effect that mods are able to use a randomly generated key + * if they really *really* want to avoid other code touching their data. + */ + +void ModApiIPC::Initialize(lua_State *L, int top) +{ + FATAL_ERROR_IF(!getGameDef(L)->getModIPCStore(), "ModIPCStore missing from gamedef"); + + API_FCT(ipc_get); + API_FCT(ipc_set); +} diff --git a/src/script/lua_api/l_ipc.h b/src/script/lua_api/l_ipc.h new file mode 100644 index 000000000..ca2cde22f --- /dev/null +++ b/src/script/lua_api/l_ipc.h @@ -0,0 +1,15 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "lua_api/l_base.h" + +class ModApiIPC : public ModApiBase { +private: + static int l_ipc_get(lua_State *L); + static int l_ipc_set(lua_State *L); + +public: + static void Initialize(lua_State *L, int top); +}; diff --git a/src/script/scripting_emerge.cpp b/src/script/scripting_emerge.cpp index 3467b1495..f96a6c294 100644 --- a/src/script/scripting_emerge.cpp +++ b/src/script/scripting_emerge.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_util.h" #include "lua_api/l_vmanip.h" #include "lua_api/l_settings.h" +#include "lua_api/l_ipc.h" extern "C" { #include @@ -89,5 +90,6 @@ void EmergeScripting::InitializeModApi(lua_State *L, int top) ModApiMapgen::InitializeEmerge(L, top); ModApiServer::InitializeAsync(L, top); ModApiUtil::InitializeAsync(L, top); + ModApiIPC::Initialize(L, top); // TODO ^ these should also be renamed to InitializeRO or such } diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index 324850011..d7d2513bb 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -46,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_settings.h" #include "lua_api/l_http.h" #include "lua_api/l_storage.h" +#include "lua_api/l_ipc.h" extern "C" { #include @@ -121,6 +122,7 @@ void ServerScripting::initAsync() asyncEngine.registerStateInitializer(ModApiCraft::InitializeAsync); asyncEngine.registerStateInitializer(ModApiItem::InitializeAsync); asyncEngine.registerStateInitializer(ModApiServer::InitializeAsync); + asyncEngine.registerStateInitializer(ModApiIPC::Initialize); // not added: ModApiMapgen is a minefield for thread safety // not added: ModApiHttp async api can't really work together with our jobs // not added: ModApiStorage is probably not thread safe(?) @@ -176,6 +178,7 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ModApiHttp::Initialize(L, top); ModApiStorage::Initialize(L, top); ModApiChannels::Initialize(L, top); + ModApiIPC::Initialize(L, top); } void ServerScripting::InitializeAsync(lua_State *L, int top) diff --git a/src/server.cpp b/src/server.cpp index 405af63ef..df2d14a1d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -86,6 +86,15 @@ public: {} }; +ModIPCStore::~ModIPCStore() +{ + // we don't have to do this, it's pure debugging aid + if (!std::unique_lock(mutex, std::try_to_lock).owns_lock()) { + errorstream << FUNCTION_NAME << ": lock is still in use!" << std::endl; + assert(0); + } +} + class ServerThread : public Thread { public: diff --git a/src/server.h b/src/server.h index 0318ec6f6..e8fa6b0da 100644 --- a/src/server.h +++ b/src/server.h @@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include class ChatEvent; struct ChatEventChat; @@ -142,6 +143,20 @@ struct ClientInfo { std::string vers_string, lang_code; }; +struct ModIPCStore { + ModIPCStore() = default; + ~ModIPCStore(); + + /// RW lock for this entire structure + std::shared_mutex mutex; + /** + * Map storing the data + * + * @note Do not store `nil` data in this map, instead remove the whole key. + */ + std::unordered_map> map; +}; + class Server : public con::PeerHandler, public MapEventReceiver, public IGameDef { @@ -301,12 +316,14 @@ public: NodeDefManager* getWritableNodeDefManager(); IWritableCraftDefManager* getWritableCraftDefManager(); + // Not under envlock virtual const std::vector &getMods() const; virtual const ModSpec* getModSpec(const std::string &modname) const; virtual const SubgameSpec* getGameSpec() const { return &m_gamespec; } static std::string getBuiltinLuaPath(); virtual std::string getWorldPath() const { return m_path_world; } virtual std::string getModDataPath() const { return m_path_mod_data; } + virtual ModIPCStore *getModIPCStore() { return &m_ipcstore; } inline bool isSingleplayer() const { return m_simple_singleplayer_mode; } @@ -666,6 +683,8 @@ private: std::unordered_map server_translations; + ModIPCStore m_ipcstore; + /* Threads */ From 72801d0233f7ec985c7cc4144547c6640df5193f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 23 May 2024 15:44:16 +0200 Subject: [PATCH 182/200] Implement minetest.ipc_cas() --- doc/lua_api.md | 12 ++++ .../mods/unittests/inside_mapgen_env.lua | 7 ++- src/script/common/c_packer.cpp | 1 + src/script/lua_api/l_ipc.cpp | 55 +++++++++++++++++-- src/script/lua_api/l_ipc.h | 1 + 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index eb3c111b9..337b42fb0 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -7066,6 +7066,18 @@ minetest.ipc_get("test:foo").subkey = "value" -- WRONG! minetest.ipc_get("test:foo") -- returns an empty table ``` +**Advanced**: + +* `minetest.ipc_cas(key, old_value, new_value)`: + * Write a value to the shared data area, but only if the previous value + equals what was given. + This operation is called Compare-and-Swap and can be used to implement + synchronization between threads. + * `key`: as above + * `old_value`: value compared to using `==` (`nil` compares equal for non-existing keys) + * `new_value`: value that will be set + * returns: true on success, false otherwise + Bans ---- diff --git a/games/devtest/mods/unittests/inside_mapgen_env.lua b/games/devtest/mods/unittests/inside_mapgen_env.lua index 021a4a44c..f6f8513ce 100644 --- a/games/devtest/mods/unittests/inside_mapgen_env.lua +++ b/games/devtest/mods/unittests/inside_mapgen_env.lua @@ -22,8 +22,11 @@ local function do_tests() assert(core.registered_items["unittests:description_test"].on_place == true) end --- this is checked from the main env -core.ipc_set("unittests:mg", { pcall(do_tests) }) +-- first thread to get here runs the tests +if core.ipc_cas("unittests:mg_once", nil, true) then + -- this is checked from the main env + core.ipc_set("unittests:mg", { pcall(do_tests) }) +end core.register_on_generated(function(vm, pos1, pos2, blockseed) local n = tonumber(core.get_mapgen_setting("chunksize")) * 16 - 1 diff --git a/src/script/common/c_packer.cpp b/src/script/common/c_packer.cpp index 579167952..bbef89c1f 100644 --- a/src/script/common/c_packer.cpp +++ b/src/script/common/c_packer.cpp @@ -507,6 +507,7 @@ PackedValue *script_pack(lua_State *L, int idx) void script_unpack(lua_State *L, PackedValue *pv) { + assert(pv); // table that tracks objects for keep_ref / PUSHREF (key = instr index) lua_newtable(L); const int top = lua_gettop(L); diff --git a/src/script/lua_api/l_ipc.cpp b/src/script/lua_api/l_ipc.cpp index eb1eaedd7..35c6182dd 100644 --- a/src/script/lua_api/l_ipc.cpp +++ b/src/script/lua_api/l_ipc.cpp @@ -10,6 +10,17 @@ typedef std::shared_lock SharedReadLock; typedef std::unique_lock SharedWriteLock; +static inline auto read_pv(lua_State *L, int idx) +{ + std::unique_ptr ret; + if (!lua_isnil(L, idx)) { + ret.reset(script_pack(L, idx)); + if (ret->contains_userdata) + throw LuaError("Userdata not allowed"); + } + return ret; +} + int ModApiIPC::l_ipc_get(lua_State *L) { auto *store = getGameDef(L)->getModIPCStore(); @@ -34,12 +45,7 @@ int ModApiIPC::l_ipc_set(lua_State *L) auto key = readParam(L, 1); luaL_checkany(L, 2); - std::unique_ptr pv; - if (!lua_isnil(L, 2)) { - pv.reset(script_pack(L, 2)); - if (pv->contains_userdata) - throw LuaError("Userdata not allowed"); - } + auto pv = read_pv(L, 2); { SharedWriteLock autolock(store->mutex); @@ -51,6 +57,42 @@ int ModApiIPC::l_ipc_set(lua_State *L) return 0; } +int ModApiIPC::l_ipc_cas(lua_State *L) +{ + auto *store = getGameDef(L)->getModIPCStore(); + + auto key = readParam(L, 1); + + luaL_checkany(L, 2); + const int idx_old = 2; + + luaL_checkany(L, 3); + auto pv_new = read_pv(L, 3); + + bool ok = false; + { + SharedWriteLock autolock(store->mutex); + // unpack and compare old value + auto it = store->map.find(key); + if (it == store->map.end()) { + ok = lua_isnil(L, idx_old); + } else { + script_unpack(L, it->second.get()); + ok = lua_equal(L, idx_old, -1); + lua_pop(L, 1); + } + // put new value + if (ok) { + if (pv_new) + store->map[key] = std::move(pv_new); + else + store->map.erase(key); + } + } + lua_pushboolean(L, ok); + return 1; +} + /* * Implementation note: * Iterating over the IPC table is intentionally not supported. @@ -65,4 +107,5 @@ void ModApiIPC::Initialize(lua_State *L, int top) API_FCT(ipc_get); API_FCT(ipc_set); + API_FCT(ipc_cas); } diff --git a/src/script/lua_api/l_ipc.h b/src/script/lua_api/l_ipc.h index ca2cde22f..31a2b2bc1 100644 --- a/src/script/lua_api/l_ipc.h +++ b/src/script/lua_api/l_ipc.h @@ -9,6 +9,7 @@ class ModApiIPC : public ModApiBase { private: static int l_ipc_get(lua_State *L); static int l_ipc_set(lua_State *L); + static int l_ipc_cas(lua_State *L); public: static void Initialize(lua_State *L, int top); From d2b4c27f2151166b4eacffd2178e8e11cd79b5c2 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 28 May 2024 23:04:08 +0200 Subject: [PATCH 183/200] Implement minetest.ipc_poll() --- doc/lua_api.md | 9 ++++++++ games/devtest/mods/unittests/misc.lua | 15 ++++++++++++++ src/script/lua_api/l_ipc.cpp | 30 +++++++++++++++++++++++++++ src/script/lua_api/l_ipc.h | 1 + src/server.h | 6 ++++++ 5 files changed, 61 insertions(+) diff --git a/doc/lua_api.md b/doc/lua_api.md index 337b42fb0..f2f0a5ba3 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -7077,6 +7077,15 @@ minetest.ipc_get("test:foo") -- returns an empty table * `old_value`: value compared to using `==` (`nil` compares equal for non-existing keys) * `new_value`: value that will be set * returns: true on success, false otherwise +* `minetest.ipc_poll(key, timeout)`: + * Do a blocking wait until a value (other than `nil`) is present at the key. + * **IMPORTANT**: You usually don't need this function. Use this as a last resort + if nothing else can satisfy your use case! None of the Lua environments the + engine has are safe to block for extended periods, especially on the main + thread any delays directly translate to lag felt by players. + * `key`: as above + * `timeout`: maximum wait time, in milliseconds (positive values only) + * returns: true on success, false on timeout Bans ---- diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index 1b39708d9..6a2a33fa7 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -279,3 +279,18 @@ local function test_ipc_vector_preserve(cb) assert(vector.check(v)) end unittests.register("test_ipc_vector_preserve", test_ipc_vector_preserve) + +local function test_ipc_poll(cb) + core.ipc_set("unittests:flag", nil) + assert(core.ipc_poll("unittests:flag", 1) == false) + + -- Note that unlike the async result callback - which has to wait for the + -- next server step - the IPC is instant + local t0 = core.get_us_time() + core.handle_async(function() + core.ipc_set("unittests:flag", true) + end, function() end) + assert(core.ipc_poll("unittests:flag", 1000) == true, "Wait failed (or slow machine?)") + print("delta: " .. (core.get_us_time() - t0) .. "us") +end +unittests.register("test_ipc_poll", test_ipc_poll) diff --git a/src/script/lua_api/l_ipc.cpp b/src/script/lua_api/l_ipc.cpp index 35c6182dd..8b9f2aec9 100644 --- a/src/script/lua_api/l_ipc.cpp +++ b/src/script/lua_api/l_ipc.cpp @@ -6,6 +6,7 @@ #include "common/c_packer.h" #include "server.h" #include "debug.h" +#include typedef std::shared_lock SharedReadLock; typedef std::unique_lock SharedWriteLock; @@ -54,6 +55,7 @@ int ModApiIPC::l_ipc_set(lua_State *L) else store->map.erase(key); // delete the map value for nil } + store->signal(); return 0; } @@ -89,10 +91,37 @@ int ModApiIPC::l_ipc_cas(lua_State *L) store->map.erase(key); } } + + if (ok) + store->signal(); lua_pushboolean(L, ok); return 1; } +int ModApiIPC::l_ipc_poll(lua_State *L) +{ + auto *store = getGameDef(L)->getModIPCStore(); + + auto key = readParam(L, 1); + + auto timeout = std::chrono::milliseconds( + std::max(0, luaL_checkinteger(L, 2)) + ); + + bool ret; + { + SharedReadLock autolock(store->mutex); + + // wait until value exists or timeout + ret = store->condvar.wait_for(autolock, timeout, [&] () -> bool { + return store->map.count(key) != 0; + }); + } + + lua_pushboolean(L, ret); + return 1; +} + /* * Implementation note: * Iterating over the IPC table is intentionally not supported. @@ -108,4 +137,5 @@ void ModApiIPC::Initialize(lua_State *L, int top) API_FCT(ipc_get); API_FCT(ipc_set); API_FCT(ipc_cas); + API_FCT(ipc_poll); } diff --git a/src/script/lua_api/l_ipc.h b/src/script/lua_api/l_ipc.h index 31a2b2bc1..dc73a5b86 100644 --- a/src/script/lua_api/l_ipc.h +++ b/src/script/lua_api/l_ipc.h @@ -10,6 +10,7 @@ private: static int l_ipc_get(lua_State *L); static int l_ipc_set(lua_State *L); static int l_ipc_cas(lua_State *L); + static int l_ipc_poll(lua_State *L); public: static void Initialize(lua_State *L, int top); diff --git a/src/server.h b/src/server.h index e8fa6b0da..10db9e208 100644 --- a/src/server.h +++ b/src/server.h @@ -48,6 +48,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include class ChatEvent; struct ChatEventChat; @@ -149,12 +150,17 @@ struct ModIPCStore { /// RW lock for this entire structure std::shared_mutex mutex; + /// Signalled on any changes to the map contents + std::condition_variable_any condvar; /** * Map storing the data * * @note Do not store `nil` data in this map, instead remove the whole key. */ std::unordered_map> map; + + /// @note Should be called without holding the lock. + inline void signal() { condvar.notify_all(); } }; class Server : public con::PeerHandler, public MapEventReceiver, From 1b2d24791a4b6c625e694db83aa85afb8ed1818f Mon Sep 17 00:00:00 2001 From: Zemtzov7 <72821250+zmv7@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:01:22 +0500 Subject: [PATCH 184/200] Separate anticheat settings (#15040) --- builtin/settingtypes.txt | 9 +++++++-- src/defaultsettings.cpp | 5 ++++- src/migratesettings.h | 9 +++++++++ src/network/serverpackethandler.cpp | 8 ++++---- src/server.h | 14 ++++++++++++++ src/server/player_sao.cpp | 10 +++++++++- 6 files changed, 47 insertions(+), 8 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 1813a6cdf..f7225ef62 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -884,8 +884,13 @@ default_privs (Default privileges) string interact, shout # Privileges that players with basic_privs can grant basic_privs (Basic privileges) string interact, shout -# If enabled, disable cheat prevention in multiplayer. -disable_anticheat (Disable anticheat) bool false +# Server anticheat configuration. +# Flags are positive. Uncheck the flag to disable corresponding anticheat module. +anticheat_flags (Anticheat flags) flags digging,interaction,movement digging,interaction,movement + +# Tolerance of movement cheat detector. +# Increase the value if players experience stuttery movement. +anticheat_movement_tolerance (Anticheat movement tolerance) float 1.0 1.0 # If enabled, actions are recorded for rollback. # This option is only read when server starts. diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 2915caa48..049359ef1 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "mapgen/mapgen.h" // Mapgen::setDefaultSettings #include "util/string.h" +#include "server.h" /* @@ -459,7 +460,9 @@ void set_default_settings() settings->setDefault("enable_pvp", "true"); settings->setDefault("enable_mod_channels", "false"); settings->setDefault("disallow_empty_password", "false"); - settings->setDefault("disable_anticheat", "false"); + settings->setDefault("anticheat_flags", flagdesc_anticheat, + AC_DIGGING | AC_INTERACTION | AC_MOVEMENT); + settings->setDefault("anticheat_movement_tolerance", "1.0"); settings->setDefault("enable_rollback_recording", "false"); settings->setDefault("deprecated_lua_api_handling", "log"); diff --git a/src/migratesettings.h b/src/migratesettings.h index d4488702f..5f6396914 100644 --- a/src/migratesettings.h +++ b/src/migratesettings.h @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "settings.h" +#include "server.h" void migrate_settings() { @@ -19,4 +20,12 @@ void migrate_settings() g_settings->setBool("touch_gui", value); g_settings->remove("enable_touch"); } + + // Disables anticheat + if (g_settings->existsLocal("disable_anticheat")) { + if (g_settings->getBool("disable_anticheat")) { + g_settings->setFlagStr("anticheat_flags", 0, flagdesc_anticheat); + } + g_settings->remove("disable_anticheat"); + } } diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index d1e1ddacb..449e164b6 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -1011,12 +1011,12 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) /* Check that target is reasonably close */ - static thread_local const bool enable_anticheat = - !g_settings->getBool("disable_anticheat"); + static thread_local const u32 anticheat_flags = + g_settings->getFlagStr("anticheat_flags", flagdesc_anticheat, nullptr); if ((action == INTERACT_START_DIGGING || action == INTERACT_DIGGING_COMPLETED || action == INTERACT_PLACE || action == INTERACT_USE) && - enable_anticheat && !isSingleplayer()) { + (anticheat_flags & AC_INTERACTION) && !isSingleplayer()) { v3f target_pos = player_pos; if (pointed.type == POINTEDTHING_NODE) { target_pos = intToFloat(pointed.node_undersurface, BS); @@ -1119,7 +1119,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) /* Cheat prevention */ bool is_valid_dig = true; - if (enable_anticheat && !isSingleplayer()) { + if ((anticheat_flags & AC_DIGGING) && !isSingleplayer()) { v3s16 nocheat_p = playersao->getNoCheatDigPos(); float nocheat_t = playersao->getNoCheatDigTime(); playersao->noCheatDigEnd(); diff --git a/src/server.h b/src/server.h index 10db9e208..f2a9083b6 100644 --- a/src/server.h +++ b/src/server.h @@ -81,6 +81,20 @@ struct PackedValue; struct ParticleParameters; struct ParticleSpawnerParameters; +// Anticheat flags +enum { + AC_DIGGING = 0x01, + AC_INTERACTION = 0x02, + AC_MOVEMENT = 0x04 +}; + +constexpr const static FlagDesc flagdesc_anticheat[] = { + {"digging", AC_DIGGING}, + {"interaction", AC_INTERACTION}, + {"movement", AC_MOVEMENT}, + {NULL, 0} +}; + enum ClientDeletionReason { CDR_LEAVE, CDR_TIMEOUT, diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 61d328ca7..57b39d403 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -646,9 +646,12 @@ void PlayerSAO::setMaxSpeedOverride(const v3f &vel) bool PlayerSAO::checkMovementCheat() { + static thread_local const u32 anticheat_flags = + g_settings->getFlagStr("anticheat_flags", flagdesc_anticheat, nullptr); + if (m_is_singleplayer || isAttached() || - g_settings->getBool("disable_anticheat")) { + !(anticheat_flags & AC_MOVEMENT)) { m_last_good_position = m_base_position; return false; } @@ -729,6 +732,11 @@ bool PlayerSAO::checkMovementCheat() required_time = MYMAX(required_time, d_vert / s); } + static thread_local float anticheat_movement_tolerance = + std::max(g_settings->getFloat("anticheat_movement_tolerance"), 1.0f); + + required_time /= anticheat_movement_tolerance; + if (m_move_pool.grab(required_time)) { m_last_good_position = m_base_position; } else { From 4e6e8b7bf1c4a60c6d2f630367862a8645ccbf0c Mon Sep 17 00:00:00 2001 From: cx384 Date: Thu, 10 Oct 2024 20:25:02 +0200 Subject: [PATCH 185/200] Fix hotbar alignment with hud_hotbar_max_width --- src/client/hud.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 2a1acb288..3f3984d48 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -816,8 +816,6 @@ void Hud::drawHotbar(const v2s32 &pos, const v2f &offset, u16 dir, const v2f &al u16 playeritem = player->getWieldIndex(); v2s32 screen_offset(offset.X, offset.Y); - v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y); - s32 hotbar_itemcount = player->getMaxHotbarItemcount(); s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2); @@ -827,15 +825,11 @@ void Hud::drawHotbar(const v2s32 &pos, const v2f &offset, u16 dir, const v2f &al drawItems(pos, screen_offset, hotbar_itemcount, align, 0, mainlist, playeritem + 1, dir, true); } else { - v2s32 firstpos = pos; - firstpos.X += width/4; + v2s32 upper_pos = pos - v2s32(0, m_hotbar_imagesize + m_padding); - v2s32 secondpos = firstpos; - firstpos = firstpos - v2s32(0, m_hotbar_imagesize + m_padding); - - drawItems(firstpos, screen_offset, hotbar_itemcount / 2, align, 0, + drawItems(upper_pos, screen_offset, hotbar_itemcount / 2, align, 0, mainlist, playeritem + 1, dir, true); - drawItems(secondpos, screen_offset, hotbar_itemcount, align, + drawItems(pos, screen_offset, hotbar_itemcount, align, hotbar_itemcount / 2, mainlist, playeritem + 1, dir, true); } } From 2188adc0f99e1fab8806eb62e9526ecbcd74bf15 Mon Sep 17 00:00:00 2001 From: paradust7 <102263465+paradust7@users.noreply.github.com> Date: Sat, 12 Oct 2024 13:34:24 -0700 Subject: [PATCH 186/200] Ensure that null C strings do not break logging (#15255) --- src/client/game.cpp | 42 ++++++++++++++-- src/log.cpp | 47 ++++-------------- src/log.h | 43 ++++++++++++++++- src/log_internal.h | 75 +++++++++++++++++------------ src/unittest/CMakeLists.txt | 1 + src/unittest/test_logging.cpp | 90 +++++++++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+), 73 deletions(-) create mode 100644 src/unittest/test_logging.cpp diff --git a/src/client/game.cpp b/src/client/game.cpp index 32c57ec1b..1a125f492 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -882,7 +882,7 @@ private: SoundMaker *soundmaker = nullptr; ChatBackend *chat_backend = nullptr; - LogOutputBuffer m_chat_log_buf; + CaptureLogOutput m_chat_log_buf; EventManager *eventmgr = nullptr; QuicktuneShortcutter *quicktune = nullptr; @@ -930,6 +930,7 @@ private: * (as opposed to the this local caching). This can be addressed in * a later release. */ + bool m_cache_disable_escape_sequences; bool m_cache_doubletap_jump; bool m_cache_enable_clouds; bool m_cache_enable_joysticks; @@ -973,6 +974,10 @@ Game::Game() : m_chat_log_buf(g_logger), m_game_ui(new GameUI()) { + g_settings->registerChangedCallback("chat_log_level", + &settingChangedCallback, this); + g_settings->registerChangedCallback("disable_escape_sequences", + &settingChangedCallback, this); g_settings->registerChangedCallback("doubletap_jump", &settingChangedCallback, this); g_settings->registerChangedCallback("enable_clouds", @@ -1041,6 +1046,10 @@ Game::~Game() clearTextureNameCache(); + g_settings->deregisterChangedCallback("chat_log_level", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("disable_escape_sequences", + &settingChangedCallback, this); g_settings->deregisterChangedCallback("doubletap_jump", &settingChangedCallback, this); g_settings->deregisterChangedCallback("enable_clouds", @@ -1277,7 +1286,6 @@ void Game::shutdown() chat_backend->addMessage(L"", L"# Disconnected."); chat_backend->addMessage(L"", L""); - m_chat_log_buf.clear(); if (client) { client->Stop(); @@ -3208,9 +3216,27 @@ void Game::processClientEvents(CameraOrientation *cam) void Game::updateChat(f32 dtime) { + auto color_for = [](LogLevel level) -> const char* { + switch (level) { + case LL_ERROR : return "\x1b(c@#F00)"; // red + case LL_WARNING: return "\x1b(c@#EE0)"; // yellow + case LL_INFO : return "\x1b(c@#BBB)"; // grey + case LL_VERBOSE: return "\x1b(c@#888)"; // dark grey + case LL_TRACE : return "\x1b(c@#888)"; // dark grey + default : return ""; + } + }; + // Get new messages from error log buffer - while (!m_chat_log_buf.empty()) - chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get())); + std::vector entries = m_chat_log_buf.take(); + for (const auto& entry : entries) { + std::string line; + if (!m_cache_disable_escape_sequences) { + line.append(color_for(entry.level)); + } + line.append(entry.combined); + chat_backend->addMessage(L"", utf8_to_wide(line)); + } // Get new messages from client std::wstring message; @@ -4433,6 +4459,14 @@ void Game::settingChangedCallback(const std::string &setting_name, void *data) void Game::readSettings() { + LogLevel chat_log_level = Logger::stringToLevel(g_settings->get("chat_log_level")); + if (chat_log_level == LL_MAX) { + warningstream << "Supplied unrecognized chat_log_level; showing none." << std::endl; + chat_log_level = LL_NONE; + } + m_chat_log_buf.setLogLevel(chat_log_level); + + m_cache_disable_escape_sequences = g_settings->getBool("disable_escape_sequences"); m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); m_cache_enable_clouds = g_settings->getBool("enable_clouds"); m_cache_enable_joysticks = g_settings->getBool("enable_joysticks"); diff --git a/src/log.cpp b/src/log.cpp index 5fac64f5c..ae3d9a9ab 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -371,42 +371,15 @@ void StreamLogOutput::logRaw(LogLevel lev, std::string_view line) } } -void LogOutputBuffer::updateLogLevel() +void StreamProxy::fix_stream_state(std::ostream &os) { - const std::string &conf_loglev = g_settings->get("chat_log_level"); - LogLevel log_level = Logger::stringToLevel(conf_loglev); - if (log_level == LL_MAX) { - warningstream << "Supplied unrecognized chat_log_level; " - "showing none." << std::endl; - log_level = LL_NONE; - } - - m_logger.removeOutput(this); - m_logger.addOutputMaxLevel(this, log_level); -} - -void LogOutputBuffer::logRaw(LogLevel lev, std::string_view line) -{ - std::string color; - - if (!g_settings->getBool("disable_escape_sequences")) { - switch (lev) { - case LL_ERROR: // red - color = "\x1b(c@#F00)"; - break; - case LL_WARNING: // yellow - color = "\x1b(c@#EE0)"; - break; - case LL_INFO: // grey - color = "\x1b(c@#BBB)"; - break; - case LL_VERBOSE: // dark grey - case LL_TRACE: - color = "\x1b(c@#888)"; - break; - default: break; - } - } - MutexAutoLock lock(m_buffer_mutex); - m_buffer.emplace(color.append(line)); + std::ios::iostate state = os.rdstate(); + // clear error state so the stream works again + os.clear(); + if (state & std::ios::eofbit) + os << "(ostream:eofbit)"; + if (state & std::ios::badbit) + os << "(ostream:badbit)"; + if (state & std::ios::failbit) + os << "(ostream:failbit)"; } diff --git a/src/log.h b/src/log.h index ccd13acf3..0e34b2c83 100644 --- a/src/log.h +++ b/src/log.h @@ -32,21 +32,60 @@ class StreamProxy { public: StreamProxy(std::ostream *os) : m_os(os) { } + static void fix_stream_state(std::ostream &os); + template - StreamProxy& operator<<(T&& arg) { + StreamProxy& operator<<(T&& arg) + { if (m_os) { + if (!m_os->good()) + fix_stream_state(*m_os); *m_os << std::forward(arg); } return *this; } - StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) { + StreamProxy& operator<<(std::ostream& (*manip)(std::ostream&)) + { if (m_os) { + if (!m_os->good()) + fix_stream_state(*m_os); *m_os << manip; } return *this; } +private: + template + StreamProxy& emit_with_null_check(T&& arg) + { + // These calls explicitly use the templated version of operator<<, + // so that they won't use the overloads created by ADD_NULL_CHECK. + if (arg == nullptr) + return this->operator<< ("(null)"); + else + return this->operator<< (std::forward(arg)); + } + +public: + // Add specific overrides for operator<< which check for NULL string + // pointers. This is undefined behavior in the C++ spec, so emit "(null)" + // instead. These are method overloads, rather than template specializations. +#define ADD_NULL_CHECK(_type) \ + StreamProxy& operator<<(_type arg) \ + { \ + return emit_with_null_check(std::forward<_type>(arg)); \ + } + + ADD_NULL_CHECK(char*) + ADD_NULL_CHECK(unsigned char*) + ADD_NULL_CHECK(signed char*) + ADD_NULL_CHECK(const char*) + ADD_NULL_CHECK(const unsigned char*) + ADD_NULL_CHECK(const signed char*) + +#undef ADD_NULL_CHECK + private: std::ostream *m_os; }; diff --git a/src/log_internal.h b/src/log_internal.h index c8bc1b310..512a57949 100644 --- a/src/log_internal.h +++ b/src/log_internal.h @@ -123,54 +123,69 @@ private: std::ofstream m_stream; }; -class LogOutputBuffer : public ICombinedLogOutput { -public: - LogOutputBuffer(Logger &logger) : - m_logger(logger) - { - updateLogLevel(); - }; +struct LogEntry { + LogLevel level; + std::string timestamp; + std::string thread_name; + std::string text; + // "timestamp: level[thread_name]: text" + std::string combined; +}; - virtual ~LogOutputBuffer() +class CaptureLogOutput : public ILogOutput { +public: + CaptureLogOutput() = delete; + DISABLE_CLASS_COPY(CaptureLogOutput); + + CaptureLogOutput(Logger &logger) : m_logger(logger) + { + m_logger.addOutput(this); + } + + ~CaptureLogOutput() { m_logger.removeOutput(this); } - void updateLogLevel(); - - void logRaw(LogLevel lev, std::string_view line); - - void clear() + void setLogLevel(LogLevel level) { - MutexAutoLock lock(m_buffer_mutex); - m_buffer = std::queue(); + m_logger.removeOutput(this); + m_logger.addOutputMaxLevel(this, level); } - bool empty() const + void logRaw(LogLevel lev, std::string_view line) override { - MutexAutoLock lock(m_buffer_mutex); - return m_buffer.empty(); + MutexAutoLock lock(m_mutex); + m_entries.emplace_back(LogEntry{lev, "", "", std::string(line), std::string(line)}); } - std::string get() + void log(LogLevel lev, const std::string &combined, + const std::string &time, const std::string &thread_name, + std::string_view payload_text) override { - MutexAutoLock lock(m_buffer_mutex); - if (m_buffer.empty()) - return ""; - std::string s = std::move(m_buffer.front()); - m_buffer.pop(); - return s; + MutexAutoLock lock(m_mutex); + m_entries.emplace_back(LogEntry{lev, time, thread_name, std::string(payload_text), combined}); + } + + // Take the log entries currently stored, clearing the buffer. + std::vector take() + { + std::vector entries; + MutexAutoLock lock(m_mutex); + std::swap(m_entries, entries); + return entries; } private: - // g_logger serializes calls to logRaw() with a mutex, but that - // doesn't prevent get() / clear() from being called on top of it. - // This mutex prevents that. - mutable std::mutex m_buffer_mutex; - std::queue m_buffer; Logger &m_logger; + // g_logger serializes calls to log/logRaw with a mutex, but that + // doesn't prevent take() from being called on top of it. + // This mutex prevents that. + std::mutex m_mutex; + std::vector m_entries; }; + #ifdef __ANDROID__ class AndroidLogOutput : public ICombinedLogOutput { public: diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 93803c912..f546d150e 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -13,6 +13,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_filesys.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_lua.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mapblock.cpp diff --git a/src/unittest/test_logging.cpp b/src/unittest/test_logging.cpp new file mode 100644 index 000000000..dcfe5ec83 --- /dev/null +++ b/src/unittest/test_logging.cpp @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "test.h" +#include "log_internal.h" + +using std::ostream; + +class TestLogging : public TestBase +{ +public: + TestLogging() { TestManager::registerTestModule(this); } + const char *getName() { return "TestLogging"; } + + void runTests(IGameDef *gamedef); + + void testNullChecks(); + void testBitCheck(); +}; + +static TestLogging g_test_instance; + +void TestLogging::runTests(IGameDef *gamedef) +{ + TEST(testNullChecks); + TEST(testBitCheck); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestLogging::testNullChecks() +{ + CaptureLogOutput capture(g_logger); + + infostream << "Test char*: " << (char*)0 << std::endl; + infostream << "Test signed char*: " << (signed char*)0 << std::endl; + infostream << "Test unsigned char*: " << (unsigned char*)0 << std::endl; + + infostream << "Test const char*: " << (const char*)0 << std::endl; + infostream << "Test const signed char*: " << (const signed char*)0 << std::endl; + infostream << "Test const unsigned char*: " << (const unsigned char*)0 << std::endl; + + + auto logs = capture.take(); + UASSERTEQ(size_t, logs.size(), 6); + UASSERTEQ(std::string, logs[0].text, "Test char*: (null)"); + UASSERTEQ(std::string, logs[1].text, "Test signed char*: (null)"); + UASSERTEQ(std::string, logs[2].text, "Test unsigned char*: (null)"); + UASSERTEQ(std::string, logs[3].text, "Test const char*: (null)"); + UASSERTEQ(std::string, logs[4].text, "Test const signed char*: (null)"); + UASSERTEQ(std::string, logs[5].text, "Test const unsigned char*: (null)"); +} + +namespace { + class ForceEofBit {}; + class ForceFailBit {}; + class ForceBadBit {}; + + ostream& operator<<(ostream& os, ForceEofBit) + { + os.setstate(std::ios::eofbit); + return os; + } + + ostream& operator<<(ostream& os, ForceFailBit) + { + os.setstate(std::ios::failbit); + return os; + } + + ostream& operator<<(ostream& os, ForceBadBit) + { + os.setstate(std::ios::badbit); + return os; + } +} + +void TestLogging::testBitCheck() +{ + CaptureLogOutput capture(g_logger); + + infostream << "EOF is " << ForceEofBit{} << std::endl; + infostream << "Fail is " << ForceFailBit{} << std::endl; + infostream << "Bad is " << ForceBadBit{} << std::endl; + + auto logs = capture.take(); + UASSERTEQ(size_t, logs.size(), 3); + UASSERTEQ(std::string, logs[0].text, "EOF is (ostream:eofbit)"); + UASSERTEQ(std::string, logs[1].text, "Fail is (ostream:failbit)"); + UASSERTEQ(std::string, logs[2].text, "Bad is (ostream:badbit)"); +} From 5532248cd739355201a043b0064c9509dca16dfd Mon Sep 17 00:00:00 2001 From: grorp Date: Sat, 12 Oct 2024 22:34:39 +0200 Subject: [PATCH 187/200] Add missing setting callbacks for display_density_factor (#15273) --- src/client/clientlauncher.cpp | 2 ++ src/client/fontengine.cpp | 30 ++++++++++++++++-------------- src/client/hud.cpp | 2 ++ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index e99fbff42..13b87f2ef 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -72,6 +72,7 @@ ClientLauncher::~ClientLauncher() { delete input; g_settings->deregisterChangedCallback("dpi_change_notifier", setting_changed_callback, this); + g_settings->deregisterChangedCallback("display_density_factor", setting_changed_callback, this); g_settings->deregisterChangedCallback("gui_scaling", setting_changed_callback, this); delete g_fontengine; @@ -133,6 +134,7 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) guienv = m_rendering_engine->get_gui_env(); config_guienv(); g_settings->registerChangedCallback("dpi_change_notifier", setting_changed_callback, this); + g_settings->registerChangedCallback("display_density_factor", setting_changed_callback, this); g_settings->registerChangedCallback("gui_scaling", setting_changed_callback, this); g_fontengine = new FontEngine(guienv); diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index e0174e011..5475e9768 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -33,10 +33,20 @@ FontEngine *g_fontengine = nullptr; /** callback to be used on change of font size setting */ static void font_setting_changed(const std::string &name, void *userdata) { - if (g_fontengine) - g_fontengine->readSettings(); + static_cast(userdata)->readSettings(); } +static const char *settings[] = { + "font_size", "font_bold", "font_italic", "font_size_divisible_by", + "mono_font_size", "mono_font_size_divisible_by", + "font_shadow", "font_shadow_alpha", + "font_path", "font_path_bold", "font_path_italic", "font_path_bold_italic", + "mono_font_path", "mono_font_path_bold", "mono_font_path_italic", + "mono_font_path_bold_italic", + "fallback_font_path", + "dpi_change_notifier", "display_density_factor", "gui_scaling", +}; + /******************************************************************************/ FontEngine::FontEngine(gui::IGUIEnvironment* env) : m_env(env) @@ -51,24 +61,16 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) : readSettings(); - const char *settings[] = { - "font_size", "font_bold", "font_italic", "font_size_divisible_by", - "mono_font_size", "mono_font_size_divisible_by", - "font_shadow", "font_shadow_alpha", - "font_path", "font_path_bold", "font_path_italic", "font_path_bold_italic", - "mono_font_path", "mono_font_path_bold", "mono_font_path_italic", - "mono_font_path_bold_italic", - "fallback_font_path", - "dpi_change_notifier", "gui_scaling", - }; - for (auto name : settings) - g_settings->registerChangedCallback(name, font_setting_changed, NULL); + g_settings->registerChangedCallback(name, font_setting_changed, this); } /******************************************************************************/ FontEngine::~FontEngine() { + for (auto name : settings) + g_settings->deregisterChangedCallback(name, font_setting_changed, this); + cleanCache(); } diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 3f3984d48..29fc31ffa 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -61,6 +61,7 @@ Hud::Hud(Client *client, LocalPlayer *player, readScalingSetting(); g_settings->registerChangedCallback("dpi_change_notifier", setting_changed_callback, this); + g_settings->registerChangedCallback("display_density_factor", setting_changed_callback, this); g_settings->registerChangedCallback("hud_scaling", setting_changed_callback, this); for (auto &hbar_color : hbar_colors) @@ -170,6 +171,7 @@ void Hud::readScalingSetting() Hud::~Hud() { g_settings->deregisterChangedCallback("dpi_change_notifier", setting_changed_callback, this); + g_settings->deregisterChangedCallback("display_density_factor", setting_changed_callback, this); g_settings->deregisterChangedCallback("hud_scaling", setting_changed_callback, this); if (m_selection_mesh) From 6d5103900f9d8e6b78350b9e66a97c2ceaeb9be9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 7 Oct 2024 17:48:19 +0200 Subject: [PATCH 188/200] Some refactoring and fixes to VoxelArea and VoxelManip In particular this validates the edges of VoxelArea and fixes all the nonsense tests uncovered by it. --- doc/lua_api.md | 11 ++-- src/map.cpp | 16 ++--- src/map.h | 2 +- src/mapblock.cpp | 4 +- src/mapblock.h | 4 +- src/mapgen/dungeongen.cpp | 2 +- src/script/lua_api/l_vmanip.cpp | 3 + src/unittest/test_voxelarea.cpp | 113 ++++++++++++++++++++------------ src/voxel.cpp | 78 ++++++++-------------- src/voxel.h | 110 ++++++++++++++----------------- src/voxelalgorithms.cpp | 18 +++-- 11 files changed, 178 insertions(+), 183 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index f2f0a5ba3..d9e683da3 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5010,8 +5010,7 @@ Methods the `VoxelManip`. * `calc_lighting([p1, p2], [propagate_shadow])`: Calculate lighting within the `VoxelManip`. - * To be used only by a `VoxelManip` object from - `minetest.get_mapgen_object`. + * To be used only with a `VoxelManip` object from `minetest.get_mapgen_object`. * (`p1`, `p2`) is the area in which lighting is set, defaults to the whole area if left out or nil. For almost all uses these should be left out or nil to use the default. @@ -5019,9 +5018,11 @@ Methods generated mapchunk above are propagated down into the mapchunk, defaults to `true` if left out. * `update_liquids()`: Update liquid flow -* `was_modified()`: Returns `true` or `false` if the data in the voxel - manipulator had been modified since the last read from map, due to a call to - `minetest.set_data()` on the loaded area elsewhere. +* `was_modified()`: Returns `true` if the data in the voxel manipulator has been modified + since it was last read from the map. This means you have to call `get_data` again. + This only applies to a `VoxelManip` object from `minetest.get_mapgen_object`, + where the engine will keep the map and the VM in sync automatically. + * Note: this doesn't do what you think it does and is subject to removal. Don't use it! * `get_emerged_area()`: Returns actual emerged minimum and maximum positions. `VoxelArea` diff --git a/src/map.cpp b/src/map.cpp index d54ed1270..85ff0e84a 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -822,17 +822,9 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, } else { flags |= VMANIP_BLOCK_DATA_INEXIST; - /* - Mark area inexistent - */ + // Mark area inexistent VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); - // Fill with VOXELFLAG_NO_DATA - for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++) - for(s32 y=a.MinEdge.Y; y<=a.MaxEdge.Y; y++) - { - s32 i = m_area.index(a.MinEdge.X,y,z); - memset(&m_flags[i], VOXELFLAG_NO_DATA, MAP_BLOCKSIZE); - } + setFlags(a, VOXELFLAG_NO_DATA); } } /*else if (block->getNode(0, 0, 0).getContent() == CONTENT_IGNORE) @@ -848,9 +840,9 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, } void MMVManip::blitBackAll(std::map *modified_blocks, - bool overwrite_generated) + bool overwrite_generated) const { - if(m_area.getExtent() == v3s16(0,0,0)) + if (m_area.hasEmptyExtent()) return; assert(m_map); diff --git a/src/map.h b/src/map.h index a4bf45314..e3624a68d 100644 --- a/src/map.h +++ b/src/map.h @@ -333,7 +333,7 @@ public: // This is much faster with big chunks of generated data void blitBackAll(std::map * modified_blocks, - bool overwrite_generated = true); + bool overwrite_generated = true) const; /* Creates a copy of this VManip including contents, the copy will not be diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 9a27a9f3d..714b47ec1 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -178,13 +178,13 @@ void MapBlock::copyTo(VoxelManipulator &dst) getPosRelative(), data_size); } -void MapBlock::copyFrom(VoxelManipulator &dst) +void MapBlock::copyFrom(const VoxelManipulator &src) { v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1)); // Copy from VoxelManipulator to data - dst.copyTo(data, data_area, v3s16(0,0,0), + src.copyTo(data, data_area, v3s16(0,0,0), getPosRelative(), data_size); } diff --git a/src/mapblock.h b/src/mapblock.h index 843daf6ef..044c104bc 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -307,8 +307,8 @@ public: // Copies data to VoxelManipulator to getPosRelative() void copyTo(VoxelManipulator &dst); - // Copies data from VoxelManipulator getPosRelative() - void copyFrom(VoxelManipulator &dst); + // Copies data from VoxelManipulator to getPosRelative() + void copyFrom(const VoxelManipulator &src); // Update is air flag. // Sets m_is_air to appropriate value. diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp index 1d439abeb..0369bdac3 100644 --- a/src/mapgen/dungeongen.cpp +++ b/src/mapgen/dungeongen.cpp @@ -91,7 +91,7 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) random.seed(bseed + 2); // Dungeon generator doesn't modify places which have this set - vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE); + vm->clearFlags(vm->m_area, VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE); if (dp.only_in_ground) { // Set all air and liquid drawtypes to be untouchable to make dungeons generate diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index 76b5aff0f..72ecc55b6 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -333,6 +333,9 @@ int LuaVoxelManip::l_was_modified(lua_State *L) LuaVoxelManip *o = checkObject(L, 1); MMVManip *vm = o->vm; + if (!o->is_mapgen_vm) + log_deprecated(L, "was_modified called for a non-mapgen VoxelManip object"); + lua_pushboolean(L, vm->m_is_dirty); return 1; diff --git a/src/unittest/test_voxelarea.cpp b/src/unittest/test_voxelarea.cpp index 386fe499c..1139e72fa 100644 --- a/src/unittest/test_voxelarea.cpp +++ b/src/unittest/test_voxelarea.cpp @@ -38,6 +38,7 @@ public: void test_equal(); void test_plus(); void test_minor(); + void test_diff(); void test_intersect(); void test_index_xyz_all_pos(); void test_index_xyz_x_neg(); @@ -75,6 +76,7 @@ void TestVoxelArea::runTests(IGameDef *gamedef) TEST(test_equal); TEST(test_plus); TEST(test_minor); + TEST(test_diff); TEST(test_intersect); TEST(test_index_xyz_all_pos); TEST(test_index_xyz_x_neg); @@ -100,21 +102,21 @@ void TestVoxelArea::runTests(IGameDef *gamedef) void TestVoxelArea::test_addarea() { - VoxelArea v1(v3s16(-1447, 8854, -875), v3s16(-147, -9547, 669)); - VoxelArea v2(v3s16(-887, 4445, -5478), v3s16(447, -8779, 4778)); + VoxelArea v1(v3s16(-1447, -9547, -875), v3s16(-147, 8854, 669)); + VoxelArea v2(v3s16(-887, -8779, -5478), v3s16(447, 4445, 4778)); v1.addArea(v2); - UASSERT(v1.MinEdge == v3s16(-1447, 4445, -5478)); - UASSERT(v1.MaxEdge == v3s16(447, -8779, 4778)); + UASSERT(v1.MinEdge == v3s16(-1447, -9547, -5478)); + UASSERT(v1.MaxEdge == v3s16(447, 8854, 4778)); } void TestVoxelArea::test_pad() { - VoxelArea v1(v3s16(-1447, 8854, -875), v3s16(-147, -9547, 669)); + VoxelArea v1(v3s16(-1447, -9547, -875), v3s16(-147, 8854, 669)); v1.pad(v3s16(100, 200, 300)); - UASSERT(v1.MinEdge == v3s16(-1547, 8654, -1175)); - UASSERT(v1.MaxEdge == v3s16(-47, -9347, 969)); + UASSERT(v1.MinEdge == v3s16(-1547, -9747, -1175)); + UASSERT(v1.MaxEdge == v3s16(-47, 9054, 969)); } void TestVoxelArea::test_extent() @@ -124,6 +126,9 @@ void TestVoxelArea::test_extent() VoxelArea v2(v3s16(32493, -32507, 32752), v3s16(32508, -32492, 32767)); UASSERT(v2.getExtent() == v3s16(16, 16, 16)); + + UASSERT(VoxelArea({2,3,4}, {1,2,3}).hasEmptyExtent()); + UASSERT(VoxelArea({2,3,4}, {2,2,3}).hasEmptyExtent() == false); } void TestVoxelArea::test_volume() @@ -133,6 +138,9 @@ void TestVoxelArea::test_volume() VoxelArea v2(v3s16(32493, -32507, 32752), v3s16(32508, -32492, 32767)); UASSERTEQ(s32, v2.getVolume(), 4096); + + UASSERTEQ(s32, VoxelArea({2,3,4}, {1,2,3}).getVolume(), 0); + UASSERTEQ(s32, VoxelArea({2,3,4}, {2,2,3}).getVolume(), 0); } void TestVoxelArea::test_contains_voxelarea() @@ -185,8 +193,7 @@ void TestVoxelArea::test_equal() VoxelArea v1(v3s16(-1337, -9547, -789), v3s16(-147, 750, 669)); UASSERTEQ(bool, v1 == VoxelArea(v3s16(-1337, -9547, -789), v3s16(-147, 750, 669)), true); - UASSERTEQ(bool, v1 == VoxelArea(v3s16(0, 0, 0), v3s16(-147, 750, 669)), false); - UASSERTEQ(bool, v1 == VoxelArea(v3s16(0, 0, 0), v3s16(-147, 750, 669)), false); + UASSERTEQ(bool, v1 == VoxelArea(v3s16(-147, 0, 0), v3s16(0, 750, 669)), false); UASSERTEQ(bool, v1 == VoxelArea(v3s16(0, 0, 0), v3s16(0, 0, 0)), false); } @@ -212,6 +219,30 @@ void TestVoxelArea::test_minor() VoxelArea(v3s16(-10, -10, -45), v3s16(100, 100, 65))); } +void TestVoxelArea::test_diff() +{ + const VoxelArea v1({-10, -10, -10}, {100, 100, 100}); + std::vector res; + + v1.diff(VoxelArea({-10, -10, -10}, {99, 100, 100}), res); + UASSERTEQ(auto, res.size(), 1U); + UASSERT(res[0] == VoxelArea({100, -10, -10}, {100, 100, 100})); + res.clear(); + + v1.diff(VoxelArea({-10, -10, -10}, {100, 50, 80}), res); + UASSERTEQ(auto, res.size(), 2U); + UASSERT(res[0] == VoxelArea({-10, -10, 81}, {100, 100, 100})); + UASSERT(res[1] == VoxelArea({-10, 51, -10}, {100, 100, 80})); + res.clear(); + + // edge cases + v1.diff(v1, res); + UASSERT(res.empty()); + v1.diff(VoxelArea(), res); + UASSERTEQ(auto, res.size(), 1U); + UASSERT(res[0] == v1); +} + void TestVoxelArea::test_intersect() { VoxelArea v1({-10, -10, -10}, {10, 10, 10}); @@ -231,8 +262,8 @@ void TestVoxelArea::test_index_xyz_all_pos() VoxelArea v1; UASSERTEQ(s32, v1.index(156, 25, 236), 155); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(156, 25, 236), 1267138774); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(156, 25, 236), 1310722495); } void TestVoxelArea::test_index_xyz_x_neg() @@ -240,8 +271,8 @@ void TestVoxelArea::test_index_xyz_x_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(-147, 25, 366), -148); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(-147, 25, 366), -870244825); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(-147, 25, 366), -821642064); } void TestVoxelArea::test_index_xyz_y_neg() @@ -249,8 +280,8 @@ void TestVoxelArea::test_index_xyz_y_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(247, -269, 100), 246); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(247, -269, 100), -989760747); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(247, -269, 100), -951958678); } void TestVoxelArea::test_index_xyz_z_neg() @@ -258,8 +289,8 @@ void TestVoxelArea::test_index_xyz_z_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(244, 336, -887), 243); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(244, 336, -887), -191478876); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(244, 336, -887), -190690273); } void TestVoxelArea::test_index_xyz_xy_neg() @@ -267,8 +298,8 @@ void TestVoxelArea::test_index_xyz_xy_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(-365, -47, 6978), -366); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(-365, -47, 6978), 1493679101); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(-365, -47, 6978), 1797427926); } void TestVoxelArea::test_index_xyz_yz_neg() @@ -276,8 +307,8 @@ void TestVoxelArea::test_index_xyz_yz_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(66, -58, -789), 65); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(66, -58, -789), 1435362734); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(66, -58, -789), 1439223357); } void TestVoxelArea::test_index_xyz_xz_neg() @@ -285,8 +316,8 @@ void TestVoxelArea::test_index_xyz_xz_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(-36, 589, -992), -37); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(-36, 589, -992), -1934371362); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(-36, 589, -992), -1937179681); } void TestVoxelArea::test_index_xyz_all_neg() @@ -294,8 +325,8 @@ void TestVoxelArea::test_index_xyz_all_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(-88, -99, -1474), -89); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(-88, -99, -1474), -1343473846); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(-88, -99, -1474), -1366133749); } void TestVoxelArea::test_index_v3s16_all_pos() @@ -303,8 +334,8 @@ void TestVoxelArea::test_index_v3s16_all_pos() VoxelArea v1; UASSERTEQ(s32, v1.index(v3s16(156, 25, 236)), 155); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(v3s16(156, 25, 236)), 1267138774); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(v3s16(156, 25, 236)), 1310722495); } void TestVoxelArea::test_index_v3s16_x_neg() @@ -312,8 +343,8 @@ void TestVoxelArea::test_index_v3s16_x_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(v3s16(-147, 25, 366)), -148); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(v3s16(-147, 25, 366)), -870244825); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(v3s16(-147, 25, 366)), -821642064); } void TestVoxelArea::test_index_v3s16_y_neg() @@ -321,8 +352,8 @@ void TestVoxelArea::test_index_v3s16_y_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(v3s16(247, -269, 100)), 246); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(v3s16(247, -269, 100)), -989760747); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(v3s16(247, -269, 100)), -951958678); } void TestVoxelArea::test_index_v3s16_z_neg() @@ -330,8 +361,8 @@ void TestVoxelArea::test_index_v3s16_z_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(v3s16(244, 336, -887)), 243); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(v3s16(244, 336, -887)), -191478876); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(v3s16(244, 336, -887)), -190690273); } void TestVoxelArea::test_index_v3s16_xy_neg() @@ -339,8 +370,8 @@ void TestVoxelArea::test_index_v3s16_xy_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(v3s16(-365, -47, 6978)), -366); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(v3s16(-365, -47, 6978)), 1493679101); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(v3s16(-365, -47, 6978)), 1797427926); } void TestVoxelArea::test_index_v3s16_yz_neg() @@ -348,8 +379,8 @@ void TestVoxelArea::test_index_v3s16_yz_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(v3s16(66, -58, -789)), 65); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(v3s16(66, -58, -789)), 1435362734); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(v3s16(66, -58, -789)), 1439223357); } void TestVoxelArea::test_index_v3s16_xz_neg() @@ -357,8 +388,8 @@ void TestVoxelArea::test_index_v3s16_xz_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(v3s16(-36, 589, -992)), -37); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(v3s16(-36, 589, -992)), -1934371362); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(v3s16(-36, 589, -992)), -1937179681); } void TestVoxelArea::test_index_v3s16_all_neg() @@ -366,8 +397,8 @@ void TestVoxelArea::test_index_v3s16_all_neg() VoxelArea v1; UASSERTEQ(s32, v1.index(v3s16(-88, -99, -1474)), -89); - VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669)); - UASSERTEQ(s32, v2.index(v3s16(-88, -99, -1474)), -1343473846); + VoxelArea v2(v3s16(-147, -9547, -875), v3s16(756, 8854, 669)); + UASSERTEQ(s32, v2.index(v3s16(-88, -99, -1474)), -1366133749); } void TestVoxelArea::test_add_x() diff --git a/src/voxel.cpp b/src/voxel.cpp index 0f87cf282..9fb310962 100644 --- a/src/voxel.cpp +++ b/src/voxel.cpp @@ -29,10 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc., /* Debug stuff */ -u64 addarea_time = 0; u64 emerge_time = 0; u64 emerge_load_time = 0; -u64 clearflag_time = 0; VoxelManipulator::~VoxelManipulator() { @@ -53,7 +51,7 @@ void VoxelManipulator::clear() } void VoxelManipulator::print(std::ostream &o, const NodeDefManager *ndef, - VoxelPrintMode mode) + VoxelPrintMode mode) const { const v3s16 &em = m_area.getExtent(); v3s16 of = m_area.MinEdge; @@ -140,8 +138,6 @@ void VoxelManipulator::addArea(const VoxelArea &area) if(m_area.contains(area)) return; - TimeTaker timer("addArea", &addarea_time); - // Calculate new area VoxelArea new_area; // New area is the requested area if m_area has zero volume @@ -158,15 +154,6 @@ void VoxelManipulator::addArea(const VoxelArea &area) s32 new_size = new_area.getVolume(); - /*dstream<<"adding area "; - area.print(dstream); - dstream<<", old area "; - m_area.print(dstream); - dstream<<", new area "; - new_area.print(dstream); - dstream<<", new_size="< &result) + template + void diff(const VoxelArea &a, C &result) const { - /* - This can result in a maximum of 6 areas - */ - // If a is an empty area, return the current area as a whole - if(a.getExtent() == v3s16(0,0,0)) + if(a.hasEmptyExtent()) { VoxelArea b = *this; - if(b.getVolume() != 0) + if (b.getVolume() != 0) result.push_back(b); return; } assert(contains(a)); // pre-condition + const auto &take = [&result] (v3s16 min, v3s16 max) { + VoxelArea b(min, max); + if (b.getVolume() != 0) + result.push_back(b); + }; + // Take back area, XY inclusive { v3s16 min(MinEdge.X, MinEdge.Y, a.MaxEdge.Z+1); v3s16 max(MaxEdge.X, MaxEdge.Y, MaxEdge.Z); - VoxelArea b(min, max); - if(b.getVolume() != 0) - result.push_back(b); + take(min, max); } // Take front area, XY inclusive { v3s16 min(MinEdge.X, MinEdge.Y, MinEdge.Z); v3s16 max(MaxEdge.X, MaxEdge.Y, a.MinEdge.Z-1); - VoxelArea b(min, max); - if(b.getVolume() != 0) - result.push_back(b); + take(min, max); } // Take top area, X inclusive { v3s16 min(MinEdge.X, a.MaxEdge.Y+1, a.MinEdge.Z); v3s16 max(MaxEdge.X, MaxEdge.Y, a.MaxEdge.Z); - VoxelArea b(min, max); - if(b.getVolume() != 0) - result.push_back(b); + take(min, max); } // Take bottom area, X inclusive { v3s16 min(MinEdge.X, MinEdge.Y, a.MinEdge.Z); v3s16 max(MaxEdge.X, a.MinEdge.Y-1, a.MaxEdge.Z); - VoxelArea b(min, max); - if(b.getVolume() != 0) - result.push_back(b); + take(min, max); } // Take left area, non-inclusive { v3s16 min(MinEdge.X, a.MinEdge.Y, a.MinEdge.Z); v3s16 max(a.MinEdge.X-1, a.MaxEdge.Y, a.MaxEdge.Z); - VoxelArea b(min, max); - if(b.getVolume() != 0) - result.push_back(b); + take(min, max); } // Take right area, non-inclusive { v3s16 min(a.MaxEdge.X+1, a.MinEdge.Y, a.MinEdge.Z); v3s16 max(MaxEdge.X, a.MaxEdge.Y, a.MaxEdge.Z); - VoxelArea b(min, max); - if(b.getVolume() != 0) - result.push_back(b); + take(min, max); } - } /* @@ -344,30 +334,34 @@ public: << "=" << getVolume(); } - // Edges are inclusive + /// Minimum edge of the area (inclusive) + /// @warning read-only! v3s16 MinEdge = v3s16(1,1,1); + /// Maximum edge of the area (inclusive) + /// @warning read-only! v3s16 MaxEdge; + private: void cacheExtent() { m_cache_extent = MaxEdge - MinEdge + v3s16(1,1,1); + // If positions were sorted correctly this must always hold. + // Note that this still permits empty areas (where MinEdge = MaxEdge + 1). + assert(m_cache_extent.X >= 0); + assert(m_cache_extent.Y >= 0); + assert(m_cache_extent.Z >= 0); } v3s16 m_cache_extent = v3s16(0,0,0); }; -// unused -#define VOXELFLAG_UNUSED (1 << 0) -// no data about that node -#define VOXELFLAG_NO_DATA (1 << 1) -// Algorithm-dependent -#define VOXELFLAG_CHECKED1 (1 << 2) -// Algorithm-dependent -#define VOXELFLAG_CHECKED2 (1 << 3) -// Algorithm-dependent -#define VOXELFLAG_CHECKED3 (1 << 4) -// Algorithm-dependent -#define VOXELFLAG_CHECKED4 (1 << 5) +enum : u8 { + VOXELFLAG_NO_DATA = 1 << 0, // no data about that node + VOXELFLAG_CHECKED1 = 1 << 1, // Algorithm-dependent + VOXELFLAG_CHECKED2 = 1 << 2, // Algorithm-dependent + VOXELFLAG_CHECKED3 = 1 << 3, // Algorithm-dependent + VOXELFLAG_CHECKED4 = 1 << 4, // Algorithm-dependent +}; enum VoxelPrintMode { @@ -414,7 +408,7 @@ public: return m_data[index]; } - MapNode getNodeNoExNoEmerge(const v3s16 &p) + MapNode getNodeNoExNoEmerge(const v3s16 &p) const { if (!m_area.contains(p)) return {CONTENT_IGNORE}; @@ -430,7 +424,7 @@ public: return m_data[m_area.index(p)]; } - const MapNode & getNodeRefUnsafeCheckFlags(const v3s16 &p) + const MapNode & getNodeRefUnsafeCheckFlags(const v3s16 &p) const { s32 index = m_area.index(p); @@ -483,10 +477,13 @@ public: virtual void clear(); void print(std::ostream &o, const NodeDefManager *nodemgr, - VoxelPrintMode mode=VOXELPRINT_MATERIAL); + VoxelPrintMode mode=VOXELPRINT_MATERIAL) const; void addArea(const VoxelArea &area); + void setFlags(const VoxelArea &area, u8 flag); + void clearFlags(const VoxelArea &area, u8 flag); + /* Copy data and set flags to 0 dst_area.getExtent() <= src_area.getExtent() @@ -496,13 +493,7 @@ public: // Copy data void copyTo(MapNode *dst, const VoxelArea& dst_area, - v3s16 dst_pos, v3s16 from_pos, const v3s16 &size); - - /* - Algorithms - */ - - void clearFlag(u8 flag); + v3s16 dst_pos, v3s16 from_pos, const v3s16 &size) const; /* Member variables @@ -510,13 +501,12 @@ public: /* The area that is stored in m_data. - addInternalBox should not be used if getExtent() == v3s16(0,0,0) - MaxEdge is 1 higher than maximum allowed position + MaxEdge is 1 higher than maximum allowed position. */ VoxelArea m_area; /* - nullptr if data size is 0 (extent (0,0,0)) + nullptr if data size is 0 (empty extent) Data is stored as [z*h*w + y*h + x] */ MapNode *m_data = nullptr; diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index 607d6716c..6d159fcf2 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -943,14 +943,18 @@ bool propagate_block_sunlight(Map *map, const NodeDefManager *ndef, * The areas do not overlap. * Compatible with type 'direction'. */ -const VoxelArea block_pad[] = { - VoxelArea(v3s16(15, 0, 0), v3s16(15, 15, 15)), //X+ - VoxelArea(v3s16(1, 15, 0), v3s16(14, 15, 15)), //Y+ - VoxelArea(v3s16(1, 1, 15), v3s16(14, 14, 15)), //Z+ - VoxelArea(v3s16(1, 1, 0), v3s16(14, 14, 0)), //Z- - VoxelArea(v3s16(1, 0, 0), v3s16(14, 0, 15)), //Y- - VoxelArea(v3s16(0, 0, 0), v3s16(0, 15, 15)) //X- +#define B_1 (MAP_BLOCKSIZE - 1) +#define B_2 (MAP_BLOCKSIZE - 2) +const static VoxelArea block_pad[] = { + VoxelArea({B_1, 0, 0}, {B_1, B_1, B_1}), //X+ + VoxelArea({1, B_1, 0}, {B_2, B_1, B_1}), //Y+ + VoxelArea({1, 1, B_1}, {B_2, B_2, B_1}), //Z+ + VoxelArea({1, 1, 0}, {B_2, B_2, 0}), //Z- + VoxelArea({1, 0, 0}, {B_2, 0, B_1}), //Y- + VoxelArea({0, 0, 0}, {0, B_1, B_1}) //X- }; +#undef B_1 +#undef B_2 /*! * The common part of bulk light updates - it is always executed. From 41091a147ce52e843198b261d929840197a78227 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 7 Oct 2024 18:12:18 +0200 Subject: [PATCH 189/200] Handle VOXELFLAG_NO_DATA when in VManip get_data() --- src/script/lua_api/l_vmanip.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index 72ecc55b6..ca7ad7971 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -71,8 +71,7 @@ int LuaVoxelManip::l_get_data(lua_State *L) bool use_buffer = lua_istable(L, 2); MMVManip *vm = o->vm; - - u32 volume = vm->m_area.getVolume(); + const u32 volume = vm->m_area.getVolume(); if (use_buffer) lua_pushvalue(L, 2); @@ -80,7 +79,8 @@ int LuaVoxelManip::l_get_data(lua_State *L) lua_createtable(L, volume, 0); for (u32 i = 0; i != volume; i++) { - lua_Integer cid = vm->m_data[i].getContent(); + // Do not push unintialized data to Lua + lua_Integer cid = (vm->m_flags[i] & VOXELFLAG_NO_DATA) ? CONTENT_IGNORE : vm->m_data[i].getContent(); lua_pushinteger(L, cid); lua_rawseti(L, -2, i + 1); } @@ -108,6 +108,12 @@ int LuaVoxelManip::l_set_data(lua_State *L) lua_pop(L, 1); } + // FIXME: in theory we should clear VOXELFLAG_NO_DATA here + // However there is no way to tell which values Lua code has intended to set + // (if they were VOXELFLAG_NO_DATA before), and which were just not touched. + // In practice this doesn't cause problems because read_from_map() will cause + // all covered blocks to be loaded anyway. + return 0; } @@ -231,8 +237,7 @@ int LuaVoxelManip::l_get_light_data(lua_State *L) bool use_buffer = lua_istable(L, 2); MMVManip *vm = o->vm; - - u32 volume = vm->m_area.getVolume(); + const u32 volume = vm->m_area.getVolume(); if (use_buffer) lua_pushvalue(L, 2); @@ -240,7 +245,8 @@ int LuaVoxelManip::l_get_light_data(lua_State *L) lua_createtable(L, volume, 0); for (u32 i = 0; i != volume; i++) { - lua_Integer light = vm->m_data[i].param1; + // Do not push unintialized data to Lua + lua_Integer light = (vm->m_flags[i] & VOXELFLAG_NO_DATA) ? 0 : vm->m_data[i].getParam1(); lua_pushinteger(L, light); lua_rawseti(L, -2, i + 1); } @@ -280,8 +286,7 @@ int LuaVoxelManip::l_get_param2_data(lua_State *L) bool use_buffer = lua_istable(L, 2); MMVManip *vm = o->vm; - - u32 volume = vm->m_area.getVolume(); + const u32 volume = vm->m_area.getVolume(); if (use_buffer) lua_pushvalue(L, 2); @@ -289,7 +294,8 @@ int LuaVoxelManip::l_get_param2_data(lua_State *L) lua_createtable(L, volume, 0); for (u32 i = 0; i != volume; i++) { - lua_Integer param2 = vm->m_data[i].param2; + // Do not push unintialized data to Lua + lua_Integer param2 = (vm->m_flags[i] & VOXELFLAG_NO_DATA) ? 0 : vm->m_data[i].getParam2(); lua_pushinteger(L, param2); lua_rawseti(L, -2, i + 1); } From c8dc9c2b8ddccf03dc5ccd8de8a0778d27ac35a2 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 7 Oct 2024 21:02:32 +0200 Subject: [PATCH 190/200] Increase safety checks around ObjectRefs --- src/script/common/c_content.cpp | 2 ++ src/script/cpp_api/s_base.cpp | 21 +++++++++++++-------- src/script/lua_api/l_object.cpp | 4 +++- src/script/lua_api/l_object.h | 6 ++++-- src/unittest/mock_serveractiveobject.h | 2 +- src/unittest/test_moveaction.cpp | 4 ++++ 6 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 2e5873bbf..9ad067742 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -2236,12 +2236,14 @@ void push_pointed_thing(lua_State *L, const PointedThing &pointed, bool csm, void push_objectRef(lua_State *L, const u16 id) { + assert(id != 0); // Get core.object_refs[i] lua_getglobal(L, "core"); lua_getfield(L, -1, "object_refs"); luaL_checktype(L, -1, LUA_TTABLE); lua_pushinteger(L, id); lua_gettable(L, -2); + assert(!lua_isnoneornil(L, -1)); lua_remove(L, -2); // object_refs lua_remove(L, -2); // core } diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index bdd2514e6..cd74b7cfd 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -474,7 +474,7 @@ void ScriptApiBase::addObjectReference(ServerActiveObject *cobj) int objectstable = lua_gettop(L); // object_refs[id] = object - lua_pushnumber(L, cobj->getId()); // Push id + lua_pushinteger(L, cobj->getId()); // Push id lua_pushvalue(L, object); // Copy object to top of stack lua_settable(L, objectstable); } @@ -491,24 +491,29 @@ void ScriptApiBase::removeObjectReference(ServerActiveObject *cobj) int objectstable = lua_gettop(L); // Get object_refs[id] - lua_pushnumber(L, cobj->getId()); // Push id + lua_pushinteger(L, cobj->getId()); // Push id lua_gettable(L, objectstable); // Set object reference to NULL - ObjectRef::set_null(L); + ObjectRef::set_null(L, cobj); lua_pop(L, 1); // pop object // Set object_refs[id] = nil - lua_pushnumber(L, cobj->getId()); // Push id + lua_pushinteger(L, cobj->getId()); // Push id lua_pushnil(L); lua_settable(L, objectstable); } -// Creates a new anonymous reference if cobj=NULL or id=0 -void ScriptApiBase::objectrefGetOrCreate(lua_State *L, - ServerActiveObject *cobj) +void ScriptApiBase::objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj) { assert(getType() == ScriptingType::Server); - if (cobj == NULL || cobj->getId() == 0) { + if (!cobj) { + ObjectRef::create(L, nullptr); // dummy reference + } else if (cobj->getId() == 0) { + // TODO after 5.10.0: convert this to a FATAL_ERROR + errorstream << "ScriptApiBase::objectrefGetOrCreate(): " + << "Pushing orphan ObjectRef. Please open a bug report for this." + << std::endl; + assert(0); ObjectRef::create(L, cobj); } else { push_objectRef(L, cobj->getId()); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index ae863502f..7a199e458 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2776,9 +2776,11 @@ void ObjectRef::create(lua_State *L, ServerActiveObject *object) lua_setmetatable(L, -2); } -void ObjectRef::set_null(lua_State *L) +void ObjectRef::set_null(lua_State *L, void *expect) { ObjectRef *obj = checkObject(L, -1); + assert(obj); + FATAL_ERROR_IF(obj->m_object != expect, "ObjectRef table was messed with"); obj->m_object = nullptr; } diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 8225aa470..bc131f4f2 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -38,10 +38,12 @@ public: ~ObjectRef() = default; // Creates an ObjectRef and leaves it on top of stack - // Not callable from Lua; all references are created on the C side. + // NOTE: do not call this, use `ScriptApiBase::objectrefGetOrCreate()`! static void create(lua_State *L, ServerActiveObject *object); - static void set_null(lua_State *L); + // Clear the pointer in the ObjectRef (at -1). + // Throws an fatal error if the object pointer wasn't `expect`. + static void set_null(lua_State *L, void *expect); static void Register(lua_State *L); diff --git a/src/unittest/mock_serveractiveobject.h b/src/unittest/mock_serveractiveobject.h index 78e3df8af..995a27e55 100644 --- a/src/unittest/mock_serveractiveobject.h +++ b/src/unittest/mock_serveractiveobject.h @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class MockServerActiveObject : public ServerActiveObject { public: - MockServerActiveObject(ServerEnvironment *env = nullptr, const v3f &p = v3f()) : + MockServerActiveObject(ServerEnvironment *env = nullptr, v3f p = v3f()) : ServerActiveObject(env, p) {} virtual ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; } diff --git a/src/unittest/test_moveaction.cpp b/src/unittest/test_moveaction.cpp index 966cea8c7..b3e49341e 100644 --- a/src/unittest/test_moveaction.cpp +++ b/src/unittest/test_moveaction.cpp @@ -68,6 +68,8 @@ void TestMoveAction::runTests(IGameDef *gamedef) auto null_map = std::unique_ptr(); ServerEnvironment server_env(std::move(null_map), &server, &mb); MockServerActiveObject obj(&server_env); + obj.setId(1); + server.getScriptIface()->addObjectReference(&obj); TEST(testMove, &obj, gamedef); TEST(testMoveFillStack, &obj, gamedef); @@ -82,6 +84,8 @@ void TestMoveAction::runTests(IGameDef *gamedef) TEST(testCallbacks, &obj, &server); TEST(testCallbacksSwap, &obj, &server); + + server.getScriptIface()->removeObjectReference(&obj); } static ItemStack parse_itemstack(const char *s) From 3778ed74667c22d6a11ad82f1ed4cb60a9a51799 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 7 Oct 2024 21:08:47 +0200 Subject: [PATCH 191/200] Keep PlayerMetaRef via name not pointer --- src/script/lua_api/l_object.cpp | 2 +- src/script/lua_api/l_playermeta.cpp | 14 ++++++++++---- src/script/lua_api/l_playermeta.h | 17 ++++++++++++----- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 7a199e458..cd9be5428 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1521,7 +1521,7 @@ int ObjectRef::l_get_meta(lua_State *L) if (playersao == nullptr) return 0; - PlayerMetaRef::create(L, &playersao->getMeta()); + PlayerMetaRef::create(L, &getServer(L)->getEnv(), playersao->getPlayer()->getName()); return 1; } diff --git a/src/script/lua_api/l_playermeta.cpp b/src/script/lua_api/l_playermeta.cpp index e937c145c..a3377c524 100644 --- a/src/script/lua_api/l_playermeta.cpp +++ b/src/script/lua_api/l_playermeta.cpp @@ -21,6 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_playermeta.h" #include "lua_api/l_internal.h" #include "common/c_content.h" +#include "serverenvironment.h" +#include "remoteplayer.h" +#include "server/player_sao.h" /* PlayerMetaRef @@ -28,12 +31,15 @@ with this program; if not, write to the Free Software Foundation, Inc., IMetadata *PlayerMetaRef::getmeta(bool auto_create) { - return metadata; + auto *player = m_env->getPlayer(m_name); + auto *sao = player ? player->getPlayerSAO() : nullptr; + return sao ? &sao->getMeta() : nullptr; } void PlayerMetaRef::clearMeta() { - metadata->clear(); + if (auto *meta = getmeta(true)) + meta->clear(); } void PlayerMetaRef::reportMetadataChange(const std::string *name) @@ -43,9 +49,9 @@ void PlayerMetaRef::reportMetadataChange(const std::string *name) // Creates an PlayerMetaRef and leaves it on top of stack // Not callable from Lua; all references are created on the C side. -void PlayerMetaRef::create(lua_State *L, IMetadata *metadata) +void PlayerMetaRef::create(lua_State *L, ServerEnvironment *env, std::string_view name) { - PlayerMetaRef *o = new PlayerMetaRef(metadata); + PlayerMetaRef *o = new PlayerMetaRef(env, name); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); diff --git a/src/script/lua_api/l_playermeta.h b/src/script/lua_api/l_playermeta.h index f07bdcd09..3f39c3755 100644 --- a/src/script/lua_api/l_playermeta.h +++ b/src/script/lua_api/l_playermeta.h @@ -24,12 +24,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_metadata.h" #include "irrlichttypes_bloated.h" #include "inventory.h" -#include "metadata.h" + +class ServerEnvironment; class PlayerMetaRef : public MetaDataRef { private: - IMetadata *metadata = nullptr; + ServerEnvironment *m_env; + std::string m_name; static const luaL_Reg methods[]; @@ -40,12 +42,17 @@ private: virtual void reportMetadataChange(const std::string *name = nullptr); public: - PlayerMetaRef(IMetadata *metadata) : metadata(metadata) {} + PlayerMetaRef(ServerEnvironment *env, std::string_view name) : + m_env(env), m_name(name) + { + assert(m_env); + assert(!m_name.empty()); + } ~PlayerMetaRef() = default; - // Creates an ItemStackMetaRef and leaves it on top of stack + // Creates an PlayerMetaRef and leaves it on top of stack // Not callable from Lua; all references are created on the C side. - static void create(lua_State *L, IMetadata *metadata); + static void create(lua_State *L, ServerEnvironment *env, std::string_view name); static void Register(lua_State *L); From dbf103da326479fdae87852a207e360692341dd2 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 7 Oct 2024 21:15:05 +0200 Subject: [PATCH 192/200] Fix hexadecimal line number in abort msgs --- src/debug.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/debug.cpp b/src/debug.cpp index 04d59a6d7..8a9704dd7 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -54,7 +54,7 @@ void sanity_check_fn(const char *assertion, const char *file, #endif errorstream << std::endl << "In thread " << std::hex - << std::this_thread::get_id() << ":" << std::endl; + << std::this_thread::get_id() << ":\n" << std::dec; errorstream << file << ":" << line << ": " << function << ": An engine assumption '" << assertion << "' failed." << std::endl; @@ -69,7 +69,7 @@ void fatal_error_fn(const char *msg, const char *file, #endif errorstream << std::endl << "In thread " << std::hex - << std::this_thread::get_id() << ":" << std::endl; + << std::this_thread::get_id() << ":\n" << std::dec; errorstream << file << ":" << line << ": " << function << ": A fatal error occurred: " << msg << std::endl; From 99b6315c1a9805a8c2af3b1834b2a3ccfc5438c4 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 8 Oct 2024 12:14:40 +0200 Subject: [PATCH 193/200] Make logging respect stream flushes also add override keyword and fix overflow() behavior --- src/util/stream.h | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/util/stream.h b/src/util/stream.h index 620ad74ba..dbbe22592 100644 --- a/src/util/stream.h +++ b/src/util/stream.h @@ -23,48 +23,55 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -template > +template > class StringStreamBuffer : public std::streambuf { public: StringStreamBuffer(Emitter emitter) : m_emitter(emitter) { buffer_index = 0; } - int overflow(int c) { - push_back(c); - return c; + int overflow(int c) override { + if (c != traits_type::eof()) + push_back(c); + return 0; } void push_back(char c) { - if (c == '\n' || c == '\r') { - if (buffer_index) - m_emitter(std::string_view(buffer, buffer_index)); - buffer_index = 0; + // emit only complete lines, or if the buffer is full + if (c == '\n') { + sync(); } else { buffer[buffer_index++] = c; if (buffer_index >= BufferLength) { - m_emitter(std::string_view(buffer, buffer_index)); - buffer_index = 0; + sync(); } } } - std::streamsize xsputn(const char *s, std::streamsize n) { + std::streamsize xsputn(const char *s, std::streamsize n) override { for (std::streamsize i = 0; i < n; ++i) push_back(s[i]); return n; } + + int sync() override { + if (buffer_index) + m_emitter(std::string_view(buffer, buffer_index)); + buffer_index = 0; + return 0; + } + private: Emitter m_emitter; + unsigned int buffer_index; char buffer[BufferLength]; - int buffer_index; }; class DummyStreamBuffer : public std::streambuf { - int overflow(int c) { - return c; + int overflow(int c) override { + return 0; } - std::streamsize xsputn(const char *s, std::streamsize n) { + std::streamsize xsputn(const char *s, std::streamsize n) override { return n; } }; From 244f4f285a227129524cf13500cbff5b06ef2383 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 9 Oct 2024 23:51:27 +0200 Subject: [PATCH 194/200] Alias MutexAutoLock to the simpler std::lock_guard --- src/threading/event.cpp | 8 +++++--- src/threading/mutex_auto_lock.h | 7 +++++-- src/threading/thread.cpp | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/threading/event.cpp b/src/threading/event.cpp index 885e732c8..1b0c5ac07 100644 --- a/src/threading/event.cpp +++ b/src/threading/event.cpp @@ -28,7 +28,7 @@ DEALINGS IN THE SOFTWARE. void Event::wait() { - MutexAutoLock lock(mutex); + std::unique_lock lock(mutex); while (!notified) { cv.wait(lock); } @@ -38,7 +38,9 @@ void Event::wait() void Event::signal() { - MutexAutoLock lock(mutex); - notified = true; + { + std::lock_guard lock(mutex); + notified = true; + } cv.notify_one(); } diff --git a/src/threading/mutex_auto_lock.h b/src/threading/mutex_auto_lock.h index c809ff8f5..9a2522557 100644 --- a/src/threading/mutex_auto_lock.h +++ b/src/threading/mutex_auto_lock.h @@ -26,5 +26,8 @@ DEALINGS IN THE SOFTWARE. #pragma once #include -using MutexAutoLock = std::unique_lock; -using RecursiveMutexAutoLock = std::unique_lock; + +/// @deprecated use std::lock_guard directly +using MutexAutoLock = std::lock_guard; +/// @deprecated use std::lock_guard directly +using RecursiveMutexAutoLock = std::lock_guard; diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index f9e356ab7..a4405287d 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -117,7 +117,7 @@ bool Thread::start() // The mutex may already be locked if the thread is being restarted // FIXME: what if this fails, or if already locked by same thread? - MutexAutoLock sf_lock(m_start_finished_mutex, std::try_to_lock); + std::unique_lock sf_lock(m_start_finished_mutex, std::try_to_lock); try { m_thread_obj = new std::thread(threadProc, this); @@ -189,7 +189,7 @@ void Thread::threadProc(Thread *thr) // Wait for the thread that started this one to finish initializing the // thread handle so that getThreadId/getThreadHandle will work. - MutexAutoLock sf_lock(thr->m_start_finished_mutex); + std::unique_lock sf_lock(thr->m_start_finished_mutex); thr->m_retval = thr->run(); From d95e916a4246fa38f0fcd79a07d1542e28336eb5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 10 Oct 2024 16:59:21 +0200 Subject: [PATCH 195/200] Defer to read_from_map in VoxelManip ctor concrete problem: the getEmergeThread safety check was missing there --- src/script/lua_api/l_vmanip.cpp | 29 ++++++++++++++--------------- src/script/lua_api/l_vmanip.h | 1 - 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index ca7ad7971..33f6f7407 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -369,36 +369,35 @@ LuaVoxelManip::LuaVoxelManip(Map *map) : vm(new MMVManip(map)) { } -LuaVoxelManip::LuaVoxelManip(Map *map, v3s16 p1, v3s16 p2) -{ - vm = new MMVManip(map); - - v3s16 bp1 = getNodeBlockPos(p1); - v3s16 bp2 = getNodeBlockPos(p2); - sortBoxVerticies(bp1, bp2); - vm->initialEmerge(bp1, bp2); -} - LuaVoxelManip::~LuaVoxelManip() { if (!is_mapgen_vm) delete vm; } -// LuaVoxelManip() +// LuaVoxelManip([p1, p2]) // Creates an LuaVoxelManip and leaves it on top of stack int LuaVoxelManip::create_object(lua_State *L) { GET_ENV_PTR; - Map *map = &(env->getMap()); - LuaVoxelManip *o = (lua_istable(L, 1) && lua_istable(L, 2)) ? - new LuaVoxelManip(map, check_v3s16(L, 1), check_v3s16(L, 2)) : - new LuaVoxelManip(map); + LuaVoxelManip *o = new LuaVoxelManip(&env->getMap()); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); + + // Call read_from_map so we don't have to duplicate it here + const int top = lua_gettop(L); + if (lua_istable(L, 1) && lua_istable(L, 2)) { + lua_pushcfunction(L, l_read_from_map); + lua_pushvalue(L, top); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_call(L, 3, 0); + } + lua_settop(L, top); + return 1; } diff --git a/src/script/lua_api/l_vmanip.h b/src/script/lua_api/l_vmanip.h index 04670bef6..820f89d5a 100644 --- a/src/script/lua_api/l_vmanip.h +++ b/src/script/lua_api/l_vmanip.h @@ -64,7 +64,6 @@ public: MMVManip *vm = nullptr; LuaVoxelManip(MMVManip *mmvm, bool is_mapgen_vm); - LuaVoxelManip(Map *map, v3s16 p1, v3s16 p2); LuaVoxelManip(Map *map); ~LuaVoxelManip(); From cbc741f4642fc644948f74239ed35f7f4ecb0d16 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 12 Oct 2024 22:26:17 +0200 Subject: [PATCH 196/200] Various improvements to push_json_value --- games/devtest/mods/unittests/misc.lua | 12 +++++++ src/script/common/c_content.cpp | 45 ++++++++++++++------------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index 6a2a33fa7..a807a390f 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -153,6 +153,18 @@ local function test_urlencode() end unittests.register("test_urlencode", test_urlencode) +local function test_parse_json() + local raw = "{\"how\\u0000weird\":\n\"yes\\u0000really\",\"n\":-1234567891011,\"z\":null}" + local data = core.parse_json(raw) + assert(data["how\000weird"] == "yes\000really") + assert(data.n == -1234567891011) + assert(data.z == nil) + local null = {} + data = core.parse_json(raw, null) + assert(data.z == null) +end +unittests.register("test_parse_json", test_parse_json) + local function test_game_info() local info = minetest.get_game_info() local game_conf = Settings(info.path .. "/game.conf") diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 9ad067742..348c2559a 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -2063,6 +2063,10 @@ bool read_tree_def(lua_State *L, int idx, const NodeDefManager *ndef, } /******************************************************************************/ +#if defined(JSONCPP_STRING) || !(JSONCPP_VERSION_MAJOR < 1 || JSONCPP_VERSION_MINOR < 9) +#define HAVE_JSON_STRING +#endif + // Returns depth of json value tree static int push_json_value_getdepth(const Json::Value &value) { @@ -2070,11 +2074,8 @@ static int push_json_value_getdepth(const Json::Value &value) return 1; int maxdepth = 0; - for (const auto &it : value) { - int elemdepth = push_json_value_getdepth(it); - if (elemdepth > maxdepth) - maxdepth = elemdepth; - } + for (const auto &it : value) + maxdepth = std::max(push_json_value_getdepth(it), maxdepth); return maxdepth + 1; } // Recursive function to convert JSON --> Lua table @@ -2087,41 +2088,40 @@ static bool push_json_value_helper(lua_State *L, const Json::Value &value, lua_pushvalue(L, nullindex); break; case Json::intValue: - lua_pushinteger(L, value.asLargestInt()); - break; case Json::uintValue: - lua_pushinteger(L, value.asLargestUInt()); - break; case Json::realValue: + // push everything as a double since Lua integers may be too small lua_pushnumber(L, value.asDouble()); break; - case Json::stringValue: - { - const char *str = value.asCString(); - lua_pushstring(L, str ? str : ""); - } + case Json::stringValue: { +#ifdef HAVE_JSON_STRING + const auto &str = value.asString(); + lua_pushlstring(L, str.c_str(), str.size()); +#else + const char *str = value.asCString(); + lua_pushstring(L, str ? str : ""); +#endif break; + } case Json::booleanValue: lua_pushboolean(L, value.asInt()); break; case Json::arrayValue: lua_createtable(L, value.size(), 0); - for (Json::Value::const_iterator it = value.begin(); - it != value.end(); ++it) { + for (auto it = value.begin(); it != value.end(); ++it) { push_json_value_helper(L, *it, nullindex); lua_rawseti(L, -2, it.index() + 1); } break; case Json::objectValue: lua_createtable(L, 0, value.size()); - for (Json::Value::const_iterator it = value.begin(); - it != value.end(); ++it) { -#if !defined(JSONCPP_STRING) && (JSONCPP_VERSION_MAJOR < 1 || JSONCPP_VERSION_MINOR < 9) + for (auto it = value.begin(); it != value.end(); ++it) { +#ifdef HAVE_JSON_STRING + const auto &str = it.name(); + lua_pushlstring(L, str.c_str(), str.size()); +#else const char *str = it.memberName(); lua_pushstring(L, str ? str : ""); -#else - std::string str = it.name(); - lua_pushstring(L, str.c_str()); #endif push_json_value_helper(L, *it, nullindex); lua_rawset(L, -3); @@ -2130,6 +2130,7 @@ static bool push_json_value_helper(lua_State *L, const Json::Value &value, } return true; } + // converts JSON --> Lua table; returns false if lua stack limit exceeded // nullindex: Lua stack index of value to use in place of JSON null bool push_json_value(lua_State *L, const Json::Value &value, int nullindex) From dbbe0ca065b3fff86398d64156ade42de97b57f7 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 12 Oct 2024 22:28:56 +0200 Subject: [PATCH 197/200] Update jsoncpp copy to 1.9.6 note: the version number is different due to https://github.com/open-source-parsers/jsoncpp/issues/1571 --- lib/jsoncpp/json/UPDATING | 2 +- lib/jsoncpp/json/json-forwards.h | 31 ++-- lib/jsoncpp/json/json.h | 145 +++++++++++++----- lib/jsoncpp/jsoncpp.cpp | 246 ++++++++++++++++++------------- 4 files changed, 278 insertions(+), 146 deletions(-) diff --git a/lib/jsoncpp/json/UPDATING b/lib/jsoncpp/json/UPDATING index c96ade51c..f299a33ba 100644 --- a/lib/jsoncpp/json/UPDATING +++ b/lib/jsoncpp/json/UPDATING @@ -1,6 +1,6 @@ #!/bin/sh cd .. -git clone https://github.com/open-source-parsers/jsoncpp -b 1.9.5 --depth 1 +git clone https://github.com/open-source-parsers/jsoncpp -b 1.9.6 --depth 1 cd jsoncpp ./amalgamate.py cp -R dist/json ../json diff --git a/lib/jsoncpp/json/json-forwards.h b/lib/jsoncpp/json/json-forwards.h index dda924f83..6800f787f 100644 --- a/lib/jsoncpp/json/json-forwards.h +++ b/lib/jsoncpp/json/json-forwards.h @@ -1,4 +1,4 @@ -/// Json-cpp amalgamated forward header (http://jsoncpp.sourceforge.net/). +/// Json-cpp amalgamated forward header (https://github.com/open-source-parsers/jsoncpp/). /// It is intended to be used with #include "json/json-forwards.h" /// This header provides forward declaration for all JsonCpp types. @@ -94,19 +94,18 @@ license you like. // 3. /CMakeLists.txt // IMPORTANT: also update the SOVERSION!! -#define JSONCPP_VERSION_STRING "1.9.5" +#define JSONCPP_VERSION_STRING "1.9.7" #define JSONCPP_VERSION_MAJOR 1 #define JSONCPP_VERSION_MINOR 9 -#define JSONCPP_VERSION_PATCH 5 +#define JSONCPP_VERSION_PATCH 7 #define JSONCPP_VERSION_QUALIFIER #define JSONCPP_VERSION_HEXA \ ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ (JSONCPP_VERSION_PATCH << 8)) -#ifdef JSONCPP_USING_SECURE_MEMORY -#undef JSONCPP_USING_SECURE_MEMORY +#if !defined(JSONCPP_USE_SECURE_MEMORY) +#define JSONCPP_USE_SECURE_MEMORY 0 #endif -#define JSONCPP_USING_SECURE_MEMORY 0 // If non-zero, the library zeroes any memory that it has allocated before // it frees its memory. @@ -133,10 +132,12 @@ license you like. #ifndef JSON_ALLOCATOR_H_INCLUDED #define JSON_ALLOCATOR_H_INCLUDED +#include #include #include -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { template class SecureAllocator { @@ -164,8 +165,16 @@ public: * The memory block is filled with zeroes before being released. */ void deallocate(pointer p, size_type n) { - // memset_s is used because memset may be optimized away by the compiler + // These constructs will not be removed by the compiler during optimization, + // unlike memset. +#if defined(HAVE_MEMSET_S) memset_s(p, n * sizeof(T), 0, n * sizeof(T)); +#elif defined(_WIN32) + RtlSecureZeroMemory(p, n * sizeof(T)); +#else + std::fill_n(reinterpret_cast(p), n, 0); +#endif + // free using "global operator delete" ::operator delete(p); } @@ -195,7 +204,9 @@ public: // Boilerplate SecureAllocator() {} template SecureAllocator(const SecureAllocator&) {} - template struct rebind { using other = SecureAllocator; }; + template struct rebind { + using other = SecureAllocator; + }; }; template @@ -356,7 +367,7 @@ using LargestUInt = UInt64; template using Allocator = - typename std::conditional, + typename std::conditional, std::allocator>::type; using String = std::basic_string, Allocator>; using IStringStream = diff --git a/lib/jsoncpp/json/json.h b/lib/jsoncpp/json/json.h index b280790a4..d81f7dc0f 100644 --- a/lib/jsoncpp/json/json.h +++ b/lib/jsoncpp/json/json.h @@ -1,4 +1,4 @@ -/// Json-cpp amalgamated header (http://jsoncpp.sourceforge.net/). +/// Json-cpp amalgamated header (https://github.com/open-source-parsers/jsoncpp/). /// It is intended to be used with #include "json/json.h" // ////////////////////////////////////////////////////////////////////// @@ -93,19 +93,18 @@ license you like. // 3. /CMakeLists.txt // IMPORTANT: also update the SOVERSION!! -#define JSONCPP_VERSION_STRING "1.9.5" +#define JSONCPP_VERSION_STRING "1.9.7" #define JSONCPP_VERSION_MAJOR 1 #define JSONCPP_VERSION_MINOR 9 -#define JSONCPP_VERSION_PATCH 5 +#define JSONCPP_VERSION_PATCH 7 #define JSONCPP_VERSION_QUALIFIER #define JSONCPP_VERSION_HEXA \ ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ (JSONCPP_VERSION_PATCH << 8)) -#ifdef JSONCPP_USING_SECURE_MEMORY -#undef JSONCPP_USING_SECURE_MEMORY +#if !defined(JSONCPP_USE_SECURE_MEMORY) +#define JSONCPP_USE_SECURE_MEMORY 0 #endif -#define JSONCPP_USING_SECURE_MEMORY 0 // If non-zero, the library zeroes any memory that it has allocated before // it frees its memory. @@ -132,10 +131,12 @@ license you like. #ifndef JSON_ALLOCATOR_H_INCLUDED #define JSON_ALLOCATOR_H_INCLUDED +#include #include #include -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { template class SecureAllocator { @@ -163,8 +164,16 @@ public: * The memory block is filled with zeroes before being released. */ void deallocate(pointer p, size_type n) { - // memset_s is used because memset may be optimized away by the compiler + // These constructs will not be removed by the compiler during optimization, + // unlike memset. +#if defined(HAVE_MEMSET_S) memset_s(p, n * sizeof(T), 0, n * sizeof(T)); +#elif defined(_WIN32) + RtlSecureZeroMemory(p, n * sizeof(T)); +#else + std::fill_n(reinterpret_cast(p), n, 0); +#endif + // free using "global operator delete" ::operator delete(p); } @@ -194,7 +203,9 @@ public: // Boilerplate SecureAllocator() {} template SecureAllocator(const SecureAllocator&) {} - template struct rebind { using other = SecureAllocator; }; + template struct rebind { + using other = SecureAllocator; + }; }; template @@ -355,7 +366,7 @@ using LargestUInt = UInt64; template using Allocator = - typename std::conditional, + typename std::conditional, std::allocator>::type; using String = std::basic_string, Allocator>; using IStringStream = @@ -459,7 +470,8 @@ class ValueConstIterator; #include "forwards.h" #endif // if !defined(JSON_IS_AMALGAMATION) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { @@ -527,8 +539,8 @@ public: // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef JSON_H_INCLUDED -#define JSON_H_INCLUDED +#ifndef JSON_VALUE_H_INCLUDED +#define JSON_VALUE_H_INCLUDED #if !defined(JSON_IS_AMALGAMATION) #include "forwards.h" @@ -577,7 +589,8 @@ public: #pragma warning(disable : 4251 4275) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() /** \brief JSON (JavaScript Object Notation). */ @@ -898,7 +911,7 @@ public: int compare(const Value& other) const; const char* asCString() const; ///< Embedded zeroes could cause you trouble! -#if JSONCPP_USING_SECURE_MEMORY +#if JSONCPP_USE_SECURE_MEMORY unsigned getCStringLength() const; // Allows you to understand the length of // the CString #endif @@ -960,7 +973,7 @@ public: /// \post type() is arrayValue void resize(ArrayIndex newSize); - //@{ + ///@{ /// Access an array element (zero based index). If the array contains less /// than index element, then null value are inserted in the array so that /// its size is index+1. @@ -968,15 +981,15 @@ public: /// this from the operator[] which takes a string.) Value& operator[](ArrayIndex index); Value& operator[](int index); - //@} + ///@} - //@{ + ///@{ /// Access an array element (zero based index). /// (You may need to say 'value[0u]' to get your compiler to distinguish /// this from the operator[] which takes a string.) const Value& operator[](ArrayIndex index) const; const Value& operator[](int index) const; - //@} + ///@} /// If the array contains at least index+1 elements, returns the element /// value, otherwise returns defaultValue. @@ -1036,6 +1049,9 @@ public: /// and operator[]const /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + Value const* find(const String& key) const; /// Most general and efficient version of object-mutators. /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. @@ -1108,6 +1124,26 @@ public: iterator begin(); iterator end(); + /// \brief Returns a reference to the first element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + const Value& front() const; + + /// \brief Returns a reference to the first element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + Value& front(); + + /// \brief Returns a reference to the last element in the `Value`. + /// Requires that value holds an array or json object, with at least one + /// element. + const Value& back() const; + + /// \brief Returns a reference to the last element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + Value& back(); + // Accessors for the [start, limit) range of bytes within the JSON text from // which this value was parsed, if any. void setOffsetStart(ptrdiff_t start); @@ -1448,6 +1484,14 @@ public: inline void swap(Value& a, Value& b) { a.swap(b); } +inline const Value& Value::front() const { return *begin(); } + +inline Value& Value::front() { return *begin(); } + +inline const Value& Value::back() const { return *(--end()); } + +inline Value& Value::back() { return *(--end()); } + } // namespace Json #pragma pack(pop) @@ -1496,7 +1540,8 @@ inline void swap(Value& a, Value& b) { a.swap(b); } #pragma warning(disable : 4251) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { @@ -1523,12 +1568,12 @@ public: }; /** \brief Constructs a Reader allowing all features for parsing. - * \deprecated Use CharReader and CharReaderBuilder. + * \deprecated Use CharReader and CharReaderBuilder. */ Reader(); /** \brief Constructs a Reader allowing the specified feature set for parsing. - * \deprecated Use CharReader and CharReaderBuilder. + * \deprecated Use CharReader and CharReaderBuilder. */ Reader(const Features& features); @@ -1662,6 +1707,7 @@ private: using Errors = std::deque; bool readToken(Token& token); + bool readTokenSkippingComments(Token& token); void skipSpaces(); bool match(const Char* pattern, int patternLength); bool readComment(); @@ -1693,7 +1739,6 @@ private: int& column) const; String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); static bool containsNewLine(Location begin, Location end); static String normalizeEOL(Location begin, Location end); @@ -1716,6 +1761,12 @@ private: */ class JSON_API CharReader { public: + struct JSON_API StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + virtual ~CharReader() = default; /** \brief Read a Value from a JSON * document. The document must be a UTF-8 encoded string containing the @@ -1734,7 +1785,12 @@ public: * error occurred. */ virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, - String* errs) = 0; + String* errs); + + /** \brief Returns a vector of structured errors encountered while parsing. + * Each parse call resets the stored list of errors. + */ + std::vector getStructuredErrors() const; class JSON_API Factory { public: @@ -1744,7 +1800,21 @@ public: */ virtual CharReader* newCharReader() const = 0; }; // Factory -}; // CharReader + +protected: + class Impl { + public: + virtual ~Impl() = default; + virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) = 0; + virtual std::vector getStructuredErrors() const = 0; + }; + + explicit CharReader(std::unique_ptr impl) : _impl(std::move(impl)) {} + +private: + std::unique_ptr _impl; +}; // CharReader /** \brief Build a CharReader implementation. * @@ -1832,6 +1902,12 @@ public: * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode */ static void strictMode(Json::Value* settings); + /** ECMA-404 mode. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderECMA404Mode + */ + static void ecma404Mode(Json::Value* settings); }; /** Consume entire stream and use its begin/end. @@ -1912,7 +1988,8 @@ JSON_API IStream& operator>>(IStream&, Value&); #pragma warning(disable : 4251) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { @@ -1955,7 +2032,7 @@ public: */ virtual StreamWriter* newStreamWriter() const = 0; }; // Factory -}; // StreamWriter +}; // StreamWriter /** \brief Write into stringstream, then return string, for convenience. * A StreamWriter will be created from the factory, used, and then deleted. @@ -2059,8 +2136,7 @@ public: #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSON_API FastWriter - : public Writer { +class JSON_API FastWriter : public Writer { public: FastWriter(); ~FastWriter() override = default; @@ -2109,7 +2185,7 @@ private: * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * If the Value have comments then they are outputed according to their + * If the Value have comments then they are outputted according to their *#CommentPlacement. * * \sa Reader, Value, Value::setComment() @@ -2119,8 +2195,7 @@ private: #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSON_API - StyledWriter : public Writer { +class JSON_API StyledWriter : public Writer { public: StyledWriter(); ~StyledWriter() override = default; @@ -2178,7 +2253,7 @@ private: * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * If the Value have comments then they are outputed according to their + * If the Value have comments then they are outputted according to their #CommentPlacement. * * \sa Reader, Value, Value::setComment() @@ -2188,8 +2263,7 @@ private: #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSON_API - StyledStreamWriter { +class JSON_API StyledStreamWriter { public: /** * \param indentation Each level will be indented by this amount extra. @@ -2245,6 +2319,7 @@ String JSON_API valueToString( PrecisionType precisionType = PrecisionType::significantDigits); String JSON_API valueToString(bool value); String JSON_API valueToQuotedString(const char* value); +String JSON_API valueToQuotedString(const char* value, size_t length); /// \brief Output using the StyledStreamWriter. /// \see Json::operator>>() diff --git a/lib/jsoncpp/jsoncpp.cpp b/lib/jsoncpp/jsoncpp.cpp index 2ec052c94..758f451c6 100644 --- a/lib/jsoncpp/jsoncpp.cpp +++ b/lib/jsoncpp/jsoncpp.cpp @@ -1,4 +1,4 @@ -/// Json-cpp amalgamated source (http://jsoncpp.sourceforge.net/). +/// Json-cpp amalgamated source (https://github.com/open-source-parsers/jsoncpp/). /// It is intended to be used with #include "json/json.h" // ////////////////////////////////////////////////////////////////////// @@ -250,6 +250,7 @@ Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) { #endif // if !defined(JSON_IS_AMALGAMATION) #include #include +#include #include #include #include @@ -366,7 +367,7 @@ bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, bool successful = readValue(); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); if (collectComments_ && !commentsBefore_.empty()) root.setComment(commentsBefore_, commentAfter); if (features_.strictRoot_) { @@ -394,7 +395,7 @@ bool Reader::readValue() { throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -462,14 +463,14 @@ bool Reader::readValue() { return successful; } -void Reader::skipCommentTokens(Token& token) { +bool Reader::readTokenSkippingComments(Token& token) { + bool success = readToken(token); if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); + while (success && token.type_ == tokenComment) { + success = readToken(token); + } } + return success; } bool Reader::readToken(Token& token) { @@ -683,12 +684,7 @@ bool Reader::readObject(Token& token) { Value init(objectValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; + while (readTokenSkippingComments(tokenName)) { if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object return true; name.clear(); @@ -717,15 +713,11 @@ bool Reader::readObject(Token& token) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { + if (!readTokenSkippingComments(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } @@ -755,10 +747,7 @@ bool Reader::readArray(Token& token) { Token currentToken; // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } + ok = readTokenSkippingComments(currentToken); bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { @@ -836,11 +825,16 @@ bool Reader::decodeDouble(Token& token) { bool Reader::decodeDouble(Token& token, Value& decoded) { double value = 0; - String buffer(token.start_, token.end_); - IStringStream is(buffer); - if (!(is >> value)) - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); + IStringStream is(String(token.start_, token.end_)); + if (!(is >> value)) { + if (value == std::numeric_limits::max()) + value = std::numeric_limits::infinity(); + else if (value == std::numeric_limits::lowest()) + value = -std::numeric_limits::infinity(); + else if (!std::isinf(value)) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); + } decoded = value; return true; } @@ -1004,7 +998,7 @@ void Reader::getLocationLineAndColumn(Location location, int& line, while (current < location && current != end_) { Char c = *current++; if (c == '\r') { - if (*current == '\n') + if (current != end_ && *current == '\n') ++current; lastLineStart = current; ++line; @@ -1121,17 +1115,12 @@ class OurReader { public: using Char = char; using Location = const Char*; - struct StructuredError { - ptrdiff_t offset_start; - ptrdiff_t offset_limit; - String message; - }; explicit OurReader(OurFeatures const& features); bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true); String getFormattedErrorMessages() const; - std::vector getStructuredErrors() const; + std::vector getStructuredErrors() const; private: OurReader(OurReader const&); // no impl @@ -1174,6 +1163,7 @@ private: using Errors = std::deque; bool readToken(Token& token); + bool readTokenSkippingComments(Token& token); void skipSpaces(); void skipBom(bool skipBom); bool match(const Char* pattern, int patternLength); @@ -1207,7 +1197,6 @@ private: int& column) const; String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); static String normalizeEOL(Location begin, Location end); static bool containsNewLine(Location begin, Location end); @@ -1261,7 +1250,7 @@ bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, bool successful = readValue(); nodes_.pop(); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) { addError("Extra non-whitespace after JSON value.", token); return false; @@ -1289,7 +1278,7 @@ bool OurReader::readValue() { if (nodes_.size() > features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -1376,14 +1365,14 @@ bool OurReader::readValue() { return successful; } -void OurReader::skipCommentTokens(Token& token) { +bool OurReader::readTokenSkippingComments(Token& token) { + bool success = readToken(token); if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); + while (success && token.type_ == tokenComment) { + success = readToken(token); + } } + return success; } bool OurReader::readToken(Token& token) { @@ -1680,12 +1669,7 @@ bool OurReader::readObject(Token& token) { Value init(objectValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; + while (readTokenSkippingComments(tokenName)) { if (tokenName.type_ == tokenObjectEnd && (name.empty() || features_.allowTrailingCommas_)) // empty object or trailing comma @@ -1722,15 +1706,11 @@ bool OurReader::readObject(Token& token) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { + if (!readTokenSkippingComments(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } @@ -1764,10 +1744,7 @@ bool OurReader::readArray(Token& token) { Token currentToken; // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } + ok = readTokenSkippingComments(currentToken); bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { @@ -1845,7 +1822,7 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) { const auto digit(static_cast(c - '0')); if (value >= threshold) { // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, meaing value == threshold, + // a) we've only just touched the limit, meaning value == threshold, // b) this is the last digit, or // c) it's small enough to fit in that rounding delta, we're okay. // Otherwise treat this number as a double to avoid overflow. @@ -1882,11 +1859,15 @@ bool OurReader::decodeDouble(Token& token) { bool OurReader::decodeDouble(Token& token, Value& decoded) { double value = 0; - const String buffer(token.start_, token.end_); - IStringStream is(buffer); + IStringStream is(String(token.start_, token.end_)); if (!(is >> value)) { - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); + if (value == std::numeric_limits::max()) + value = std::numeric_limits::infinity(); + else if (value == std::numeric_limits::lowest()) + value = -std::numeric_limits::infinity(); + else if (!std::isinf(value)) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); } decoded = value; return true; @@ -2051,7 +2032,7 @@ void OurReader::getLocationLineAndColumn(Location location, int& line, while (current < location && current != end_) { Char c = *current++; if (c == '\r') { - if (*current == '\n') + if (current != end_ && *current == '\n') ++current; lastLineStart = current; ++line; @@ -2086,10 +2067,11 @@ String OurReader::getFormattedErrorMessages() const { return formattedMessage; } -std::vector OurReader::getStructuredErrors() const { - std::vector allErrors; +std::vector +OurReader::getStructuredErrors() const { + std::vector allErrors; for (const auto& error : errors_) { - OurReader::StructuredError structured; + CharReader::StructuredError structured; structured.offset_start = error.token_.start_ - begin_; structured.offset_limit = error.token_.end_ - begin_; structured.message = error.message_; @@ -2099,20 +2081,36 @@ std::vector OurReader::getStructuredErrors() const { } class OurCharReader : public CharReader { - bool const collectComments_; - OurReader reader_; public: OurCharReader(bool collectComments, OurFeatures const& features) - : collectComments_(collectComments), reader_(features) {} - bool parse(char const* beginDoc, char const* endDoc, Value* root, - String* errs) override { - bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); - if (errs) { - *errs = reader_.getFormattedErrorMessages(); + : CharReader( + std::unique_ptr(new OurImpl(collectComments, features))) {} + +protected: + class OurImpl : public Impl { + public: + OurImpl(bool collectComments, OurFeatures const& features) + : collectComments_(collectComments), reader_(features) {} + + bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) override { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; } - return ok; - } + + std::vector + getStructuredErrors() const override { + return reader_.getStructuredErrors(); + } + + private: + bool const collectComments_; + OurReader reader_; + }; }; CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } @@ -2201,6 +2199,32 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) { (*settings)["skipBom"] = true; //! [CharReaderBuilderDefaults] } +// static +void CharReaderBuilder::ecma404Mode(Json::Value* settings) { + //! [CharReaderBuilderECMA404Mode] + (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = false; + (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = false; + //! [CharReaderBuilderECMA404Mode] +} + +std::vector +CharReader::getStructuredErrors() const { + return _impl->getStructuredErrors(); +} + +bool CharReader::parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) { + return _impl->parse(beginDoc, endDoc, root, errs); +} ////////////////////////////////// // global functions @@ -2209,7 +2233,7 @@ bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, String* errs) { OStringStream ssin; ssin << sin.rdbuf(); - String doc = ssin.str(); + String doc = std::move(ssin).str(); char const* begin = doc.data(); char const* end = begin + doc.size(); // Note that we do not actually need a null-terminator. @@ -2501,7 +2525,8 @@ template static inline bool InRange(double d, T min, U max) { // The casts can lose precision, but we are looking only for // an approximate range. Might fail on edge cases though. ~cdunn - return d >= static_cast(min) && d <= static_cast(max); + return d >= static_cast(min) && d <= static_cast(max) && + !(static_cast(d) == min && d != static_cast(min)); } #else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) static inline double integerToDouble(Json::UInt64 value) { @@ -2515,7 +2540,8 @@ template static inline double integerToDouble(T value) { template static inline bool InRange(double d, T min, U max) { - return d >= integerToDouble(min) && d <= integerToDouble(max); + return d >= integerToDouble(min) && d <= integerToDouble(max) && + !(static_cast(d) == min && d != integerToDouble(min)); } #endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) @@ -2577,7 +2603,7 @@ inline static void decodePrefixedString(bool isPrefixed, char const* prefixed, /** Free the string duplicated by * duplicateStringValue()/duplicateAndPrefixStringValue(). */ -#if JSONCPP_USING_SECURE_MEMORY +#if JSONCPP_USE_SECURE_MEMORY static inline void releasePrefixedStringValue(char* value) { unsigned length = 0; char const* valueDecoded; @@ -2592,10 +2618,10 @@ static inline void releaseStringValue(char* value, unsigned length) { memset(value, 0, size); free(value); } -#else // !JSONCPP_USING_SECURE_MEMORY +#else // !JSONCPP_USE_SECURE_MEMORY static inline void releasePrefixedStringValue(char* value) { free(value); } static inline void releaseStringValue(char* value, unsigned) { free(value); } -#endif // JSONCPP_USING_SECURE_MEMORY +#endif // JSONCPP_USE_SECURE_MEMORY } // namespace Json @@ -3013,7 +3039,7 @@ const char* Value::asCString() const { return this_str; } -#if JSONCPP_USING_SECURE_MEMORY +#if JSONCPP_USE_SECURE_MEMORY unsigned Value::getCStringLength() const { JSON_ASSERT_MESSAGE(type() == stringValue, "in Json::Value::asCString(): requires stringValue"); @@ -3119,6 +3145,11 @@ Value::Int64 Value::asInt64() const { JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); return Int64(value_.uint_); case realValue: + // If the double value is in proximity to minInt64, it will be rounded to + // minInt64. The correct value in this scenario is indeterminable + JSON_ASSERT_MESSAGE( + value_.real_ != minInt64, + "Double value is minInt64, precise value cannot be determined"); JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), "double out of Int64 range"); return Int64(value_.real_); @@ -3506,6 +3537,9 @@ Value const* Value::find(char const* begin, char const* end) const { return nullptr; return &(*it).second; } +Value const* Value::find(const String& key) const { + return find(key.data(), key.data() + key.length()); +} Value* Value::demand(char const* begin, char const* end) { JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, "in Json::Value::demand(begin, end): requires " @@ -3519,7 +3553,7 @@ const Value& Value::operator[](const char* key) const { return *found; } Value const& Value::operator[](const String& key) const { - Value const* found = find(key.data(), key.data() + key.length()); + Value const* found = find(key); if (!found) return nullSingleton(); return *found; @@ -3619,7 +3653,7 @@ bool Value::removeIndex(ArrayIndex index, Value* removed) { return false; } if (removed) - *removed = it->second; + *removed = std::move(it->second); ArrayIndex oldSize = size(); // shift left all items left, into the place of the "removed" for (ArrayIndex i = index; i < (oldSize - 1); ++i) { @@ -3722,8 +3756,12 @@ bool Value::isInt64() const { // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a // double, so double(maxInt64) will be rounded up to 2^63. Therefore we // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) && - value_.real_ < double(maxInt64) && IsIntegral(value_.real_); + // minInt64 is -2^63 which can be represented as a double, but since double + // values in its proximity are also rounded to -2^63, we require the value + // to be strictly greater than the limit to avoid returning 'true' for + // values that are not in the range + return value_.real_ > double(minInt64) && value_.real_ < double(maxInt64) && + IsIntegral(value_.real_); default: break; } @@ -3761,7 +3799,11 @@ bool Value::isIntegral() const { // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we // require the value to be strictly less than the limit. - return value_.real_ >= double(minInt64) && + // minInt64 is -2^63 which can be represented as a double, but since double + // values in its proximity are also rounded to -2^63, we require the value + // to be strictly greater than the limit to avoid returning 'true' for + // values that are not in the range + return value_.real_ > double(minInt64) && value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); #else return value_.real_ >= minInt && value_.real_ <= maxUInt && @@ -3824,9 +3866,8 @@ void Value::setComment(String comment, CommentPlacement placement) { // Always discard trailing newline, to aid indentation. comment.pop_back(); } - JSON_ASSERT(!comment.empty()); JSON_ASSERT_MESSAGE( - comment[0] == '\0' || comment[0] == '/', + comment.empty() || comment[0] == '/', "in Json::Value::setComment(): Comments must start with /"); comments_.set(placement, std::move(comment)); } @@ -4194,8 +4235,9 @@ String valueToString(double value, bool useSpecialFloats, if (!isfinite(value)) { static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, {"null", "-1e+9999", "1e+9999"}}; - return reps[useSpecialFloats ? 0 : 1] - [isnan(value) ? 0 : (value < 0) ? 1 : 2]; + return reps[useSpecialFloats ? 0 : 1][isnan(value) ? 0 + : (value < 0) ? 1 + : 2]; } String buffer(size_t(36), '\0'); @@ -4415,6 +4457,10 @@ String valueToQuotedString(const char* value) { return valueToQuotedStringN(value, strlen(value)); } +String valueToQuotedString(const char* value, size_t length) { + return valueToQuotedStringN(value, length); +} + // Class Writer // ////////////////////////////////////////////////////////////////// Writer::~Writer() = default; @@ -4552,7 +4598,7 @@ void StyledWriter::writeValue(const Value& value) { const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); + writeWithIndent(valueToQuotedString(name.c_str(), name.size())); document_ += " : "; writeValue(childValue); if (++it == members.end()) { @@ -4770,7 +4816,7 @@ void StyledStreamWriter::writeValue(const Value& value) { const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); + writeWithIndent(valueToQuotedString(name.c_str(), name.size())); *document_ << " : "; writeValue(childValue); if (++it == members.end()) { @@ -5308,7 +5354,7 @@ String writeString(StreamWriter::Factory const& factory, Value const& root) { OStringStream sout; StreamWriterPtr const writer(factory.newStreamWriter()); writer->write(root, &sout); - return sout.str(); + return std::move(sout).str(); } OStream& operator<<(OStream& sout, Value const& root) { From e3aa79cffb51bcf794ec9c6271d26849783b69d1 Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Sun, 13 Oct 2024 11:29:08 +0200 Subject: [PATCH 198/200] Gettext and plural support for client-side translations (#14726) --------- Co-authored-by: Ekdohibs Co-authored-by: y5nw Co-authored-by: rubenwardy --- builtin/common/misc_helpers.lua | 33 +- builtin/mainmenu/tab_content.lua | 2 +- doc/lua_api.md | 131 ++++- games/devtest/mods/testtranslations/init.lua | 26 + .../locale/testtranslations.fr.po | 9 + .../locale/translation_mo.fr.mo | Bin 0 -> 494 bytes .../locale/translation_po.fr.po | 22 + .../locale/translation_tr.fr.tr | 2 + games/devtest/mods/testtranslations/mod.conf | 3 + .../testtranslations/test_locale/readme.txt | 4 + .../test_locale/translation_mo.de.mo | Bin 0 -> 446 bytes .../test_locale/translation_po.de.po | 42 ++ .../testtranslations/translation_mo.de.po | 26 + .../testtranslations/translation_mo.fr.po | 18 + src/CMakeLists.txt | 1 + src/client/client.cpp | 8 +- src/gettext.h | 3 +- src/gettext_plural_form.cpp | 256 +++++++++ src/gettext_plural_form.h | 33 ++ src/gui/guiEngine.cpp | 46 +- src/server.cpp | 25 +- src/translation.cpp | 517 +++++++++++++++++- src/translation.h | 38 +- src/unittest/CMakeLists.txt | 1 + src/unittest/test_servermodmanager.cpp | 2 +- src/unittest/test_translations.cpp | 64 +++ src/util/string.cpp | 84 ++- src/util/string.h | 38 +- 28 files changed, 1360 insertions(+), 74 deletions(-) create mode 100644 games/devtest/mods/testtranslations/init.lua create mode 100644 games/devtest/mods/testtranslations/locale/testtranslations.fr.po create mode 100644 games/devtest/mods/testtranslations/locale/translation_mo.fr.mo create mode 100644 games/devtest/mods/testtranslations/locale/translation_po.fr.po create mode 100644 games/devtest/mods/testtranslations/locale/translation_tr.fr.tr create mode 100644 games/devtest/mods/testtranslations/mod.conf create mode 100644 games/devtest/mods/testtranslations/test_locale/readme.txt create mode 100644 games/devtest/mods/testtranslations/test_locale/translation_mo.de.mo create mode 100644 games/devtest/mods/testtranslations/test_locale/translation_po.de.po create mode 100644 games/devtest/mods/testtranslations/translation_mo.de.po create mode 100644 games/devtest/mods/testtranslations/translation_mo.fr.po create mode 100644 src/gettext_plural_form.cpp create mode 100644 src/gettext_plural_form.h create mode 100644 src/unittest/test_translations.cpp diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 2ad9b10af..d0942b2d2 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -574,12 +574,14 @@ function core.strip_colors(str) return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", "")) end -function core.translate(textdomain, str, ...) +local function translate(textdomain, str, num, ...) local start_seq - if textdomain == "" then + if textdomain == "" and num == "" then start_seq = ESCAPE_CHAR .. "T" - else + elseif num == "" then start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")" + else + start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. "@" .. num .. ")" end local arg = {n=select('#', ...), ...} local end_seq = ESCAPE_CHAR .. "E" @@ -610,8 +612,31 @@ function core.translate(textdomain, str, ...) return start_seq .. translated .. end_seq end +function core.translate(textdomain, str, ...) + return translate(textdomain, str, "", ...) +end + +function core.translate_n(textdomain, str, str_plural, n, ...) + assert (type(n) == "number") + assert (n >= 0) + assert (math.floor(n) == n) + + -- Truncate n if too large + local max = 1000000 + if n >= 2 * max then + n = n % max + max + end + if n == 1 then + return translate(textdomain, str, "1", ...) + else + return translate(textdomain, str_plural, tostring(n), ...) + end +end + function core.get_translator(textdomain) - return function(str, ...) return core.translate(textdomain or "", str, ...) end + return + (function(str, ...) return core.translate(textdomain or "", str, ...) end), + (function(str, str_plural, n, ...) return core.translate_n(textdomain or "", str, str_plural, n, ...) end) end -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua index b38f12884..9cfb96d54 100644 --- a/builtin/mainmenu/tab_content.lua +++ b/builtin/mainmenu/tab_content.lua @@ -118,7 +118,7 @@ local function get_formspec(tabview, name, tabdata) local title_and_name if selected_pkg.type == "game" then - title_and_name = selected_pkg.name + title_and_name = selected_pkg.title or selected_pkg.name else title_and_name = (selected_pkg.title or selected_pkg.name) .. "\n" .. core.colorize("#BFBFBF", selected_pkg.name) diff --git a/doc/lua_api.md b/doc/lua_api.md index d9e683da3..2c827d7ad 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -4178,10 +4178,6 @@ Translations Texts can be translated client-side with the help of `minetest.translate` and translation files. -Consider using the script `mod_translation_updater.py` in the Minetest -[modtools](https://github.com/minetest/modtools) repository to generate and -update translation files automatically from the Lua sources. - Translating a string -------------------- @@ -4189,13 +4185,15 @@ Two functions are provided to translate strings: `minetest.translate` and `minetest.get_translator`. * `minetest.get_translator(textdomain)` is a simple wrapper around - `minetest.translate`, and `minetest.get_translator(textdomain)(str, ...)` is - equivalent to `minetest.translate(textdomain, str, ...)`. + `minetest.translate` and `minetest.translate_n`. + After `local S, NS = minetest.get_translator(textdomain)`, we have + `S(str, ...)` equivalent to `minetest.translate(textdomain, str, ...)`, and + `NS(str, str_plural, n, ...)` to `minetest.translate_n(textdomain, str, str_plural, n, ...)`. It is intended to be used in the following way, so that it avoids verbose repetitions of `minetest.translate`: ```lua - local S = minetest.get_translator(textdomain) + local S, NS = minetest.get_translator(textdomain) S(str, ...) ``` @@ -4212,29 +4210,102 @@ Two functions are provided to translate strings: `minetest.translate` and arguments the translated string expects. Arguments are literal strings -- they will not be translated. -For instance, suppose we want to greet players when they join. We can do the +* `minetest.translate_n(textdomain, str, str_plural, n, ...)` translates the + string `str` with the given `textdomain` for disambiguaion. The value of + `n`, which must be a nonnegative integer, is used to decide whether to use + the singular or the plural version of the string. Depending on the locale of + the client, the choice between singular and plural might be more complicated, + but the choice will be done automatically using the value of `n`. + + You can read https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html + for more details on the differences of plurals between languages. + + Also note that plurals are only handled in .po or .mo files, and not in .tr files. + +For instance, suppose we want to greet players when they join and provide a +command that shows the amount of time since the player joined. We can do the following: ```lua -local S = minetest.get_translator("hello") +local S, NS = minetest.get_translator("hello") minetest.register_on_joinplayer(function(player) local name = player:get_player_name() minetest.chat_send_player(name, S("Hello @1, how are you today?", name)) end) +minetest.register_chatcommand("playtime", { + func = function(name) + local last_login = core.get_auth_handler().get_auth(name).last_login + local playtime = math.floor((last_login-os.time())/60) + return true, NS( + "You have been playing for @1 minute.", + "You have been playing for @1 minutes.", + minutes, tostring(minutes)) + end, +}) ``` When someone called "CoolGuy" joins the game with an old client or a client that does not have localization enabled, they will see `Hello CoolGuy, how are -you today?` +you today?`. If they use the `/playtime` command, they will see `You have been +playing for 1 minute` or (for example) `You have been playing for 4 minutes.` -However, if we have for instance a translation file named `hello.de.tr` +However, if we have for instance a translation file named `hello.de.po` containing the following: - # textdomain: hello - Hello @1, how are you today?=Hallo @1, wie geht es dir heute? +```po +msgid "" +msgstr "" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Hello @1, how are you today?" +msgstr "Hallo @1, wie geht es dir heute?" + +msgid "You have been playing for @1 minute." +msgid_plural "You have been playing for @1 minutes." +msgstr[0] "Du spielst seit @1 Minute." +msgstr[1] "Du spielst seit @1 Minuten." +``` and CoolGuy has set a German locale, they will see `Hallo CoolGuy, wie geht es -dir heute?` +dir heute?` when they join, and the `/playtime` command will show them `Du +spielst seit 1 Minute.` or (for example) `Du spielst seit 4 Minuten.` + +Creating and updating translation files +--------------------------------------- + +As an alternative to writing translation files by hand (as shown in the above +example), it is also possible to generate translation files based on the source +code. + +It is recommended to first generate a translation template. The translation +template includes translatable strings that translators can directly work on. +After creating the `locale` directory, a translation template for the above +example using the following command: + +```sh +xgettext -L lua -kS -kNS:1,2 -kminetest.translate:1c,2 -kminetest.translate_n:1c,2,3 \ + -d hello -o locale/hello.pot *.lua +``` + +The above command can also be used to update the translation template when new +translatable strings are added. + +The German translator can then create the translation file with + +```sh +msginit -l de -i locale/hello.pot -o locale/hello.de.po +``` + +and provide the translations by editing `locale/hello.de.po`. + +The translation file can be updated using + +```sh +msgmerge -U locale/hello.de.po locale/hello.pot +``` + +Refer to the [Gettext manual](https://www.gnu.org/software/gettext/manual/) for +further information on creating and updating translation files. Operations on translated strings -------------------------------- @@ -4248,8 +4319,8 @@ expected manner. However, string concatenation will still work as expected sentences by breaking them into parts; arguments should be used instead), and operations such as `minetest.colorize` which are also concatenation. -Translation file format ------------------------ +Old translation file format +--------------------------- A translation file has the suffix `.[lang].tr`, where `[lang]` is the language it corresponds to. It must be put into the `locale` subdirectory of the mod. @@ -4264,6 +4335,34 @@ The file should be a text file, with the following format: There must be no extraneous whitespace around the `=` or at the beginning or the end of the line. +Using the earlier example of greeting the player, the translation file would be + +``` +# textdomain: hello +Hello @1, how are you today?=Hallo @1, wie geht es dir heute? +``` + +For old translation files, consider using the script `mod_translation_updater.py` +in the Minetest [modtools](https://github.com/minetest/modtools) repository to +generate and update translation files automatically from the Lua sources. + +Gettext translation file format +------------------------------- + +Gettext files can also be used as translations. A translation file has the suffix +`.[lang].po` or `.[lang].mo`, depending on whether it is compiled or not, and must +also be placed in the `locale` subdirectory of the mod. The value of `textdomain` +is `msgctxt` in the gettext files. If `msgctxt` is not provided, the name of the +translation file is used instead. + +A typical entry in a `.po` file would look like: + +```po +msgctxt "textdomain" +msgid "Hello world!" +msgstr "Bonjour le monde!" +``` + Escapes ------- diff --git a/games/devtest/mods/testtranslations/init.lua b/games/devtest/mods/testtranslations/init.lua new file mode 100644 index 000000000..bb3696e7e --- /dev/null +++ b/games/devtest/mods/testtranslations/init.lua @@ -0,0 +1,26 @@ +local S, NS = minetest.get_translator("testtranslations") + +local function send_compare(name, text) + core.chat_send_player(name, ("%s | %s | %s"):format( + core.get_translated_string("", text), text, core.get_translated_string("fr", text))) +end + +minetest.register_chatcommand("testtranslations", { + params = "", + description = "Test translations", + privs = {}, + func = function(name, param) + core.chat_send_player(name, "Please ensure your locale is set to \"fr\"") + core.chat_send_player(name, "Untranslated | Client-side translation | Server-side translation (fr)") + send_compare(name, S("Testing .tr files: untranslated")) + send_compare(name, S("Testing .po files: untranslated")) + send_compare(name, S("Testing .mo files: untranslated")) + send_compare(name, S("Testing fuzzy .po entry: untranslated (expected)")) + send_compare(name, core.translate("translation_po", "Testing .po without context: untranslated")) + send_compare(name, core.translate("translation_mo", "Testing .mo without context: untranslated")) + for i = 0,4 do + send_compare(name, NS("@1: .po singular", "@1: .po plural", i, tostring(i))) + send_compare(name, NS("@1: .mo singular", "@1: .mo plural", i, tostring(i))) + end + end +}) diff --git a/games/devtest/mods/testtranslations/locale/testtranslations.fr.po b/games/devtest/mods/testtranslations/locale/testtranslations.fr.po new file mode 100644 index 000000000..2bcc6c7d4 --- /dev/null +++ b/games/devtest/mods/testtranslations/locale/testtranslations.fr.po @@ -0,0 +1,9 @@ +# Dummy entry. This is a test to make sure that a parser error is not thrown +# if the following line is msgctxt. +msgctxt "testtranslations" +msgid "Dummy entry" +msgstr "Dummy result" + +# Used for translating the mod title +msgid "Test translations" +msgstr "Test translations (French)" diff --git a/games/devtest/mods/testtranslations/locale/translation_mo.fr.mo b/games/devtest/mods/testtranslations/locale/translation_mo.fr.mo new file mode 100644 index 0000000000000000000000000000000000000000..0e7190de975563e0ec746fb84affeacd5313d3ec GIT binary patch literal 494 zcmaKn!A^uQ6h%>SGfUT+bf+xP!PNw0Vq#?N0(WjIbfmGoVq2nqhjHaEx$q186O*~X zFpiqoaB^GvUhlh~d;Y73<%4~226n*>=vD&)@BpUZZKdzf8hn96aKAP5zCaIf{($1^ z--USFV5i&U!a7NJ?6}tKQMyXEjuo1mcycjo$r(;oaVJX8p>jM*P1Zk;;=awIzg66L zxNqh6JAoG zOH2{7*{yVwtKL7%w5ef1!#FlQP1vwaX&xp2Cm(|%_n2zRIF&47N0|{+RBVhLtSjc< OmxEF8!(1 (French plural)" + +#, foo bar fuzzy +msgctxt "testtranslations" +msgid "Testing fuzzy .po entry: untranslated (expected)" +msgstr "Testing fuzzy .po entry: translated (wrong)" + +msgid "Testing .po without context: untranslated" +msgstr "Testing .po without context: translated" diff --git a/games/devtest/mods/testtranslations/locale/translation_tr.fr.tr b/games/devtest/mods/testtranslations/locale/translation_tr.fr.tr new file mode 100644 index 000000000..b9ac66af5 --- /dev/null +++ b/games/devtest/mods/testtranslations/locale/translation_tr.fr.tr @@ -0,0 +1,2 @@ +# textdomain: testtranslations +Testing .tr files: untranslated=Testing .tr files: translated diff --git a/games/devtest/mods/testtranslations/mod.conf b/games/devtest/mods/testtranslations/mod.conf new file mode 100644 index 000000000..1fc09cf6b --- /dev/null +++ b/games/devtest/mods/testtranslations/mod.conf @@ -0,0 +1,3 @@ +name = testtranslations +title = Test translations +description = Test mod to test translations. diff --git a/games/devtest/mods/testtranslations/test_locale/readme.txt b/games/devtest/mods/testtranslations/test_locale/readme.txt new file mode 100644 index 000000000..7a2ed4329 --- /dev/null +++ b/games/devtest/mods/testtranslations/test_locale/readme.txt @@ -0,0 +1,4 @@ +The translation files in this directory intentionally include errors (which +would be reported when someone starts the devtest game in the de locale). This +allows the unittest to check that the translation file reader also handles +files that include errors. diff --git a/games/devtest/mods/testtranslations/test_locale/translation_mo.de.mo b/games/devtest/mods/testtranslations/test_locale/translation_mo.de.mo new file mode 100644 index 0000000000000000000000000000000000000000..ffe05cd7100205ba38f331c44274392b87e821ea GIT binary patch literal 446 zcmZXOF>b;@5Jd+tP#_|NQi52QDO`{e%M}tzK@mYgZIOi_*=uWe9f3s44d4z*OOxBA z;~e3eU0Vtn`RUJ$zGweW*EtcY0eWBxT3`?I`UGw81qR^T&c7%Z{RKRNW`*bp+JknW z6Q~DWKp&t7Xdiln=J(G~d{hH@)q!*Ch^o*$&z~A6Qf8@UTxhv-i%D(7I*UR{#UhO| z8AdeYaq_|6jGLB;(0r?%xplKuB4c{JSxsL!790J}>|hPv1ZFj2!kkvYv(HQ$Fu~k_ k4gKPEJSe%!BK1;" + +msgctxt "testtranslations" +msgid "Testing .mo files: untranslated" +msgstr "Testing .mo files: translated" + +msgid "Testing .mo without context: untranslated" +msgstr "Testing .mo without context: translated" + +msgctxt "testtranslations" +msgid "@1: .mo singular" +msgid_plural "@1: .mo plural" +msgstr[0] "@1: .mo 0 and 1 (French singular)" +msgstr[1] "@1: .mo >1 (French plural)" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cad22ca6f..6dd4c05d2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -408,6 +408,7 @@ set(common_SRCS face_position_cache.cpp filesys.cpp gettext.cpp + gettext_plural_form.cpp httpfetch.cpp hud.cpp inventory.cpp diff --git a/src/client/client.cpp b/src/client/client.cpp index 0f90bca97..7feb2212d 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -841,16 +841,12 @@ bool Client::loadMedia(const std::string &data, const std::string &filename, return true; } - const char *translate_ext[] = { - ".tr", NULL - }; - name = removeStringEnd(filename, translate_ext); - if (!name.empty()) { + if (Translations::isTranslationFile(filename)) { if (from_media_push) return false; TRACESTREAM(<< "Client: Loading translation: " << "\"" << filename << "\"" << std::endl); - g_client_translations->loadTranslation(data); + g_client_translations->loadTranslation(filename, data); return true; } diff --git a/src/gettext.h b/src/gettext.h index 042729c1a..507d27e64 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -36,7 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc., // the USE_GETTEXT=0 case and can't assume that gettext is installed. #include - #define gettext(String) String + #define gettext(String) (String) + #define ngettext(String1, String2, n) ((n) == 1 ? (String1) : (String2)) #endif #define _(String) gettext(String) diff --git a/src/gettext_plural_form.cpp b/src/gettext_plural_form.cpp new file mode 100644 index 000000000..6a5322421 --- /dev/null +++ b/src/gettext_plural_form.cpp @@ -0,0 +1,256 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gettext_plural_form.h" +#include "util/string.h" + +static size_t minsize(const GettextPluralForm::Ptr &form) +{ + return form ? form->size() : 0; +} + +static size_t minsize(const GettextPluralForm::Ptr &f, const GettextPluralForm::Ptr &g) +{ + if (sizeof(g) > 0) + return std::min(minsize(f), minsize(g)); + return f ? f->size() : 0; +} + +class Identity: public GettextPluralForm +{ + public: + Identity(size_t nplurals): GettextPluralForm(nplurals) {}; + NumT operator()(const NumT n) const override + { + return n; + } +}; + +class ConstValue: public GettextPluralForm +{ + public: + ConstValue(size_t nplurals, NumT val): GettextPluralForm(nplurals), value(val) {}; + NumT operator()(const NumT n) const override + { + return value; + } + private: + NumT value; +}; + +template typename F> +class UnaryOperation: public GettextPluralForm +{ + public: + UnaryOperation(const Ptr &op): + GettextPluralForm(minsize(op)), op(op) {} + NumT operator()(const NumT n) const override + { + if (operator bool()) + return func((*op)(n)); + return 0; + } + private: + Ptr op; + static constexpr F func = {}; +}; + +template typename F> +class BinaryOperation: public GettextPluralForm +{ + public: + BinaryOperation(const Ptr &lhs, const Ptr &rhs): + GettextPluralForm(minsize(lhs, rhs)), + lhs(lhs), rhs(rhs) {} + NumT operator()(const NumT n) const override + { + if (operator bool()) + return func((*lhs)(n), (*rhs)(n)); + return 0; + } + private: + Ptr lhs, rhs; + static constexpr F func = {}; +}; + +class TernaryOperation: public GettextPluralForm +{ + public: + TernaryOperation(const Ptr &cond, const Ptr &val, const Ptr &alt): + GettextPluralForm(std::min(minsize(cond), minsize(val, alt))), + cond(cond), val(val), alt(alt) {} + NumT operator()(const NumT n) const override + { + if (operator bool()) + return (*cond)(n) ? (*val)(n) : (*alt)(n); + return 0; + } + private: + Ptr cond, val, alt; +}; + +typedef std::pair ParserResult; +typedef ParserResult (*Parser)(const size_t, const std::wstring_view &); + +static ParserResult parse_expr(const size_t nplurals, const std::wstring_view &str); + +template typename Operator> +static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t* pattern) +{ + if (!str_starts_with(res.second, pattern)) + return ParserResult(nullptr, res.second); + auto next = Parser(nplurals, res.second.substr(std::char_traits::length(pattern))); + if (!next.first) + return next; + next.first = GettextPluralForm::Ptr(new BinaryOperation(res.first, next.first)); + next.second = trim(next.second); + return next; +} + +template +static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t**) +{ + return ParserResult(nullptr, res.second); +} + +template typename Operator, template typename... Operators> +static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t** patterns) +{ + auto next = reduce_ltr(nplurals, res, patterns[0]); + if (next.first || next.second != res.second) + return next; + return reduce_ltr(nplurals, res, patterns+1); +} + +template typename Operator, template typename... Operators> +static ParserResult parse_ltr(const size_t nplurals, const std::wstring_view &str, const wchar_t** patterns) +{ + auto &&pres = Parser(nplurals, str); + if (!pres.first) + return pres; + pres.second = trim(pres.second); + while (!pres.second.empty()) { + auto next = reduce_ltr(nplurals, pres, patterns); + if (!next.first) + return pres; + next.second = trim(next.second); + pres = next; + } + return pres; +} + +static ParserResult parse_atomic(const size_t nplurals, const std::wstring_view &str) +{ + if (str.empty()) + return ParserResult(nullptr, str); + if (str[0] == 'n') + return ParserResult(new Identity(nplurals), trim(str.substr(1))); + + wchar_t* endp; + auto val = wcstoul(str.data(), &endp, 10); + return ParserResult(new ConstValue(nplurals, val), trim(str.substr(endp-str.data()))); +} + +static ParserResult parse_parenthesized(const size_t nplurals, const std::wstring_view &str) +{ + if (str.empty()) + return ParserResult(nullptr, str); + if (str[0] != '(') + return parse_atomic(nplurals, str); + auto result = parse_expr(nplurals, str.substr(1)); + if (result.first) { + if (result.second.empty() || result.second[0] != ')') + result.first = nullptr; + else + result.second = trim(result.second.substr(1)); + } + return result; +} + +static ParserResult parse_negation(const size_t nplurals, const std::wstring_view &str) +{ + if (str.empty()) + return ParserResult(nullptr, str); + if (str[0] != '!') + return parse_parenthesized(nplurals, str); + auto result = parse_negation(nplurals, trim(str.substr(1))); + if (result.first) + result.first = GettextPluralForm::Ptr(new UnaryOperation(result.first)); + return result; +} + +static ParserResult parse_multiplicative(const size_t nplurals, const std::wstring_view &str) +{ + static const wchar_t *patterns[] = { L"*", L"/", L"%" }; + return parse_ltr(nplurals, str, patterns); +} + +static ParserResult parse_additive(const size_t nplurals, const std::wstring_view &str) +{ + static const wchar_t *patterns[] = { L"+", L"-" }; + return parse_ltr(nplurals, str, patterns); +} + +static ParserResult parse_comparison(const size_t nplurals, const std::wstring_view &str) +{ + static const wchar_t *patterns[] = { L"<=", L">=", L"<", L">" }; + return parse_ltr(nplurals, str, patterns); +} + +static ParserResult parse_equality(const size_t nplurals, const std::wstring_view &str) +{ + static const wchar_t *patterns[] = { L"==", L"!=" }; + return parse_ltr(nplurals, str, patterns); +} + +static ParserResult parse_conjunction(const size_t nplurals, const std::wstring_view &str) +{ + static const wchar_t *and_pattern[] = { L"&&" }; + return parse_ltr(nplurals, str, and_pattern); +} + +static ParserResult parse_disjunction(const size_t nplurals, const std::wstring_view &str) +{ + static const wchar_t *or_pattern[] = { L"||" }; + return parse_ltr(nplurals, str, or_pattern); +} + +static ParserResult parse_ternary(const size_t nplurals, const std::wstring_view &str) +{ + auto pres = parse_disjunction(nplurals, str); + if (pres.second.empty() || pres.second[0] != '?') // no ? : + return pres; + auto cond = pres.first; + pres = parse_ternary(nplurals, trim(pres.second.substr(1))); + if (pres.second.empty() || pres.second[0] != ':') + return ParserResult(nullptr, pres.second); + auto val = pres.first; + pres = parse_ternary(nplurals, trim(pres.second.substr(1))); + return ParserResult(new TernaryOperation(cond, val, pres.first), pres.second); +} + +static ParserResult parse_expr(const size_t nplurals, const std::wstring_view &str) +{ + return parse_ternary(nplurals, trim(str)); +} + +GettextPluralForm::Ptr GettextPluralForm::parse(const size_t nplurals, const std::wstring_view &str) +{ + if (nplurals == 0) + return nullptr; + auto result = parse_expr(nplurals, str); + if (!result.second.empty()) + return nullptr; + return result.first; +} + +GettextPluralForm::Ptr GettextPluralForm::parseHeaderLine(const std::wstring_view &str) +{ + if (!str_starts_with(str, L"Plural-Forms: nplurals=") || !str_ends_with(str, L";")) + return nullptr; + auto nplurals = wcstoul(str.data()+23, nullptr, 10); + auto pos = str.find(L"plural="); + if (pos == str.npos) + return nullptr; + return parse(nplurals, str.substr(pos+7, str.size()-pos-8)); +} diff --git a/src/gettext_plural_form.h b/src/gettext_plural_form.h new file mode 100644 index 000000000..d73718965 --- /dev/null +++ b/src/gettext_plural_form.h @@ -0,0 +1,33 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once +#include +#include + +// Note that this only implements a subset of C expressions. See: +// https://git.savannah.gnu.org/gitweb/?p=gettext.git;a=blob;f=gettext-runtime/intl/plural.y +class GettextPluralForm +{ +public: + using NumT = unsigned long; + using Ptr = std::shared_ptr; + + size_t size() const + { + return nplurals; + }; + virtual NumT operator()(const NumT) const = 0; + virtual operator bool() const + { + return size() > 0; + } + virtual ~GettextPluralForm() {}; + + static GettextPluralForm::Ptr parse(const size_t nplurals, const std::wstring_view &str); + static GettextPluralForm::Ptr parseHeaderLine(const std::wstring_view &str); +protected: + GettextPluralForm(size_t nplurals): nplurals(nplurals) {}; +private: + const size_t nplurals; +}; diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 8a4e22b1d..200c26fa0 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -214,15 +214,28 @@ GUIEngine::GUIEngine(JoystickController *joystick, /******************************************************************************/ -std::string findLocaleFileInMods(const std::string &path, const std::string &filename) +std::string findLocaleFileWithExtension(const std::string &path) +{ + if (fs::PathExists(path + ".mo")) + return path + ".mo"; + if (fs::PathExists(path + ".po")) + return path + ".po"; + if (fs::PathExists(path + ".tr")) + return path + ".tr"; + return ""; +} + + +/******************************************************************************/ +std::string findLocaleFileInMods(const std::string &path, const std::string &filename_no_ext) { std::vector mods = flattenMods(getModsInPath(path, "root", true)); for (const auto &mod : mods) { - std::string ret = mod.path + DIR_DELIM "locale" DIR_DELIM + filename; - if (fs::PathExists(ret)) { + std::string ret = findLocaleFileWithExtension( + mod.path + DIR_DELIM "locale" DIR_DELIM + filename_no_ext); + if (!ret.empty()) return ret; - } } return ""; @@ -235,19 +248,26 @@ Translations *GUIEngine::getContentTranslations(const std::string &path, if (domain.empty() || lang_code.empty()) return nullptr; - std::string filename = domain + "." + lang_code + ".tr"; - std::string key = path + DIR_DELIM "locale" DIR_DELIM + filename; + std::string filename_no_ext = domain + "." + lang_code; + std::string key = path + DIR_DELIM "locale" DIR_DELIM + filename_no_ext; if (key == m_last_translations_key) return &m_last_translations; std::string trans_path = key; - ContentType type = getContentType(path); - if (type == ContentType::GAME) - trans_path = findLocaleFileInMods(path + DIR_DELIM "mods" DIR_DELIM, filename); - else if (type == ContentType::MODPACK) - trans_path = findLocaleFileInMods(path, filename); - // We don't need to search for locale files in a mod, as there's only one `locale` folder. + + switch (getContentType(path)) { + case ContentType::GAME: + trans_path = findLocaleFileInMods(path + DIR_DELIM "mods" DIR_DELIM, + filename_no_ext); + break; + case ContentType::MODPACK: + trans_path = findLocaleFileInMods(path, filename_no_ext); + break; + default: + trans_path = findLocaleFileWithExtension(trans_path); + break; + } if (trans_path.empty()) return nullptr; @@ -257,7 +277,7 @@ Translations *GUIEngine::getContentTranslations(const std::string &path, std::string data; if (fs::ReadFile(trans_path, data)) { - m_last_translations.loadTranslation(data); + m_last_translations.loadTranslation(fs::GetFilenameFromPath(trans_path.c_str()), data); } return &m_last_translations; diff --git a/src/server.cpp b/src/server.cpp index df2d14a1d..ab219043e 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2537,8 +2537,8 @@ bool Server::addMediaFile(const std::string &filename, ".png", ".jpg", ".bmp", ".tga", ".ogg", ".x", ".b3d", ".obj", ".gltf", ".glb", - // Custom translation file format - ".tr", + // Translation file formats + ".tr", ".po", ".mo", NULL }; if (removeStringEnd(filename, supported_ext).empty()) { @@ -2621,14 +2621,20 @@ void Server::fillMediaCache() void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code) { - std::string lang_suffix = "."; - lang_suffix.append(lang_code).append(".tr"); + std::string translation_formats[3] = { ".tr", ".po", ".mo" }; + std::string lang_suffixes[3]; + for (size_t i = 0; i < 3; i++) { + lang_suffixes[i].append(".").append(lang_code).append(translation_formats[i]); + } - auto include = [&] (const std::string &name, const MediaInfo &info) -> bool { + auto include = [&] (const std::string &name, const MediaInfo &info) -> bool { if (info.no_announce) return false; - if (str_ends_with(name, ".tr") && !str_ends_with(name, lang_suffix)) - return false; + for (size_t j = 0; j < 3; j++) { + if (str_ends_with(name, translation_formats[j]) && !str_ends_with(name, lang_suffixes[j])) { + return false; + } + } return true; }; @@ -4167,12 +4173,11 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code) // [] will create an entry auto *translations = &server_translations[lang_code]; - std::string suffix = "." + lang_code + ".tr"; for (const auto &i : m_media) { - if (str_ends_with(i.first, suffix)) { + if (Translations::getFileLanguage(i.first) == lang_code) { std::string data; if (fs::ReadFile(i.second.path, data, true)) { - translations->loadTranslation(data); + translations->loadTranslation(i.first, data); } } } diff --git a/src/translation.cpp b/src/translation.cpp index 5d5491e56..728789acc 100644 --- a/src/translation.cpp +++ b/src/translation.cpp @@ -19,7 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "translation.h" #include "log.h" +#include "util/hex.h" #include "util/string.h" +#include "gettext.h" #include @@ -29,10 +31,22 @@ static Translations client_translations; Translations *g_client_translations = &client_translations; #endif +const std::string_view Translations::getFileLanguage(const std::string &filename) +{ + const char *translate_ext[] = { + ".tr", ".po", ".mo", NULL + }; + auto basename = removeStringEnd(filename, translate_ext); + auto pos = basename.rfind('.'); + if (pos == basename.npos) + return ""; + return basename.substr(pos+1); +} void Translations::clear() { m_translations.clear(); + m_plural_translations.clear(); } const std::wstring &Translations::getTranslation( @@ -45,7 +59,52 @@ const std::wstring &Translations::getTranslation( return s; } -void Translations::loadTranslation(const std::string &data) +const std::wstring &Translations::getPluralTranslation( + const std::wstring &textdomain, const std::wstring &s, unsigned long int number) const +{ + std::wstring key = textdomain + L"|" + s; + auto it = m_plural_translations.find(key); + if (it != m_plural_translations.end()) { + auto n = (*(it->second.first))(number); + const std::vector &v = it->second.second; + if (n < v.size()) { + if (v[n].empty()) + return s; + return v[n]; + } + } + return s; +} + + +void Translations::addTranslation( + const std::wstring &textdomain, const std::wstring &original, const std::wstring &translated) +{ + std::wstring key = textdomain + L"|" + original; + if (!translated.empty()) { + m_translations.emplace(std::move(key), std::move(translated)); + } +} + +void Translations::addPluralTranslation( + const std::wstring &textdomain, const GettextPluralForm::Ptr &plural, const std::wstring &original, std::vector &translated) +{ + static bool warned = false; + if (!plural) { + warned = true; + if (!warned) + errorstream << "Translations: plural translation entry defined without Plural-Forms" << std::endl; + return; + } else if (translated.size() != plural->size()) { + errorstream << "Translations: incorrect number of plural translations (expected " << plural->size() << ", got " << translated.size() << ")" << std::endl; + return; + } + std::wstring key = textdomain + L"|" + original; + m_plural_translations.emplace(std::move(key), std::pair(plural, translated)); +} + + +void Translations::loadTrTranslation(const std::string &data) { std::istringstream is(data); std::string textdomain_narrow; @@ -145,11 +204,455 @@ void Translations::loadTranslation(const std::string &data) } } - std::wstring oword1 = word1.str(), oword2 = word2.str(); - if (!oword2.empty()) { - std::wstring translation_index = textdomain + L"|"; - translation_index.append(oword1); - m_translations.emplace(std::move(translation_index), std::move(oword2)); - } + addTranslation(textdomain, word1.str(), word2.str()); + } +} + + +std::wstring Translations::unescapeC(const std::wstring &str) +{ + // Process escape sequences in str as if it were a C string + std::wstring result; + size_t i = 0; + while (i < str.length()) { + if (str[i] != L'\\') { + result.push_back(str[i]); + i++; + continue; + } + i++; + if (i == str.length()) { + errorstream << "Unfinished escape sequence at the end of \"" << wide_to_utf8(str) << "\"" << std::endl; + break; + } + switch (str[i]) { + // From https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences + case L'a': result.push_back(L'\a'); break; + case L'b': result.push_back(L'\b'); break; + case L'e': result.push_back(L'\x1b'); break; + case L'f': result.push_back(L'\f'); break; + case L'n': result.push_back(L'\n'); break; + case L'r': result.push_back(L'\r'); break; + case L't': result.push_back(L'\t'); break; + case L'v': result.push_back(L'\v'); break; + case L'\\': result.push_back(L'\\'); break; + case L'\'': result.push_back(L'\''); break; + case L'"': result.push_back(L'"'); break; + case L'?': result.push_back(L'?'); break; + case L'0': case L'1': case L'2': case L'3': case L'4': case L'5': case L'6': case L'7': { + size_t j = 0; + wchar_t c = 0; + for (; j < 3 && i+j < str.length() && L'0' <= str[i+j] && str[i+j] <= L'7'; j++) { + c = c * 8 + (str[i+j] - L'0'); + } + if (c <= 0xff) { + result.push_back(c); + } + i += j; + continue; + } + case L'x': { + i++; + if (i >= str.length()) { + errorstream << "Unfinished escape sequence at the end of \"" << wide_to_utf8(str) << "\"" << std::endl; + } + char32_t c = 0; + size_t j = 0; + unsigned char v; + for (; i+j < str.length() && hex_digit_decode((char)str[i+j], v); j++) { + c = c * 16 + v; + } + if (j == 0) { + errorstream << "Invalid escape sequence \\x, ignoring" << std::endl; + continue; + } + // If character fits in 16 bits and is not part of surrogate pair, insert it. + // Otherwise, silently drop it: this is valid since \x escape sequences with + // values above 0xff are implementation-defined + if ((c < 0xd800) || (0xe000 <= c && c <= 0xffff)) { + result.push_back(c); + } + i += j; + continue; + } + case L'u': { + i++; + if (i + 4 > str.length()) { + errorstream << "Unfinished escape sequence at the end of \"" << wide_to_utf8(str) << "\"" << std::endl; + } + char16_t c = 0; + bool ok = true; + for (size_t j = 0; j < 4; j++) { + unsigned char v; + if (str[i+j] <= 0xff && hex_digit_decode((char)str[i+j], v)) { + c = c * 16 + v; + } else { + errorstream << "Invalid unicode escape sequence \"\\u" << wide_to_utf8(str.substr(i, 4)) << "\", ignoring" << std::endl; + ok = false; + break; + } + } + if (ok) { + wide_add_codepoint(result, c); + } + i += 4; + continue; + } + case L'U': { + i++; + if (i + 8 > str.length()) { + errorstream << "Unfinished escape sequence at the end of \"" << wide_to_utf8(str) << "\"" << std::endl; + } + char32_t c = 0; + bool ok = true; + for (size_t j = 0; j < 8; j++) { + unsigned char v; + if (str[i+j] <= 0xff && hex_digit_decode((char)str[i+j], v)) { + c = c * 16 + v; + } else { + errorstream << "Invalid unicode escape sequence \"\\U" << wide_to_utf8(str.substr(i, 8)) << "\", ignoring" << std::endl; + ok = false; + break; + } + } + if (ok) { + wide_add_codepoint(result, c); + } + i += 8; + continue; + } + default: { + errorstream << "Unknown escape sequence \"\\" << str[i] << "\", ignoring" << std::endl; + break; + } + } + i++; + } + return result; +} + +void Translations::loadPoEntry(const std::wstring &basefilename, const GettextPluralForm::Ptr &plural_form, const std::map &entry) +{ + // Process an entry from a PO file and add it to the translation table + // Assumes that entry[L"msgid"] is always defined + std::wstring textdomain; + auto ctx = entry.find(L"msgctxt"); + if (ctx != entry.end()) { + textdomain = ctx->second; + } else { + textdomain = basefilename; + } + std::wstring original = entry.at(L"msgid"); + + auto plural = entry.find(L"msgid_plural"); + if (plural == entry.end()) { + auto translated = entry.find(L"msgstr"); + if (translated == entry.end()) { + errorstream << "Could not load translation: entry for msgid \"" << wide_to_utf8(original) << "\" does not contain a msgstr field" << std::endl; + return; + } + addTranslation(textdomain, original, translated->second); + } else { + std::vector translations; + for (int i = 0; ; i++) { + auto translated = entry.find(L"msgstr[" + std::to_wstring(i) + L"]"); + if (translated == entry.end()) + break; + translations.push_back(translated->second); + } + addPluralTranslation(textdomain, plural_form, original, translations); + addPluralTranslation(textdomain, plural_form, plural->second, translations); + } +} + +bool Translations::inEscape(const std::wstring &line, size_t pos) +{ + if (pos == std::wstring::npos || pos == 0) + return false; + pos--; + size_t count = 0; + for (; line[pos] == L'\\'; pos--) { + count++; + if (pos == 0) + break; + } + return count % 2 == 1; +} + +std::optional> Translations::parsePoLine(const std::string &line) +{ + if (line.empty()) + return std::nullopt; + if (line[0] == '#') + return std::pair(L"#", utf8_to_wide(line.substr(1))); + + std::wstring wline = utf8_to_wide(line); + // Defend against some possibly malformed utf8 string, which + // is empty after converting to wide string + if (wline.empty()) + return std::nullopt; + + std::size_t pos = wline.find(L'"'); + std::wstring s; + if (pos == std::wstring::npos) { + errorstream << "Unable to parse po file line: " << line << std::endl; + return std::nullopt; + } + auto prefix = trim(wline.substr(0, pos)); + auto begin = pos; + while (pos < wline.size()) { + begin = wline.find(L'"', pos); + if (begin == std::wstring::npos) { + if (trim(wline.substr(pos)).empty()) { + break; + } else { + errorstream << "Excessive content at the end of po file line: " << line << std::endl; + return std::nullopt; + } + } + if (!trim(wline.substr(pos, begin-pos)).empty()) { + errorstream << "Excessive content within string concatenation in po file line: " << line << std::endl; + return std::nullopt; + } + auto end = wline.find(L'"', begin+1); + while (inEscape(wline, end)) { + end = wline.find(L'"', end+1); + } + if (end == std::wstring::npos) { + errorstream << "String extends beyond po file line: " << line << std::endl; + return std::nullopt; + } + s.append(unescapeC(wline.substr(begin+1, end-begin-1))); + pos = end+1; + } + return std::pair(prefix, s); +} + +void Translations::loadPoTranslation(const std::string &basefilename, const std::string &data) +{ + std::istringstream is(data); + std::string line; + std::map last_entry; + std::wstring last_key; + std::wstring wbasefilename = utf8_to_wide(basefilename); + GettextPluralForm::Ptr plural; + bool skip = false; + bool skip_last = false; + + while (is.good()) { + std::getline(is, line); + // Trim last character if file was using a \r\n line ending + if (line.length () > 0 && line[line.length() - 1] == '\r') + line.resize(line.length() - 1); + + auto parsed = parsePoLine(line); + if (!parsed) + continue; + auto prefix = parsed->first; + auto s = parsed->second; + + if (prefix == L"#") { + if (s[0] == L',') { + // Skip fuzzy entries + if ((s + L' ').find(L" fuzzy ") != line.npos) { + if (last_entry.empty()) + skip_last = true; + else + skip = true; + } + } + continue; + } + + if (prefix.empty()) { + // Continuation of previous line + if (last_key == L"") { + errorstream << "Unable to parse po file: continuation of non-existant previous line" << std::endl; + continue; + } + + last_entry[last_key].append(s); + continue; + } + + if (prefix == L"msgctxt" || (prefix == L"msgid" && last_entry.find(L"msgid") != last_entry.end())) { + if (last_entry.find(L"msgid") != last_entry.end()) { + if (!skip_last) { + if (last_entry[L"msgid"].empty()) { + if (last_entry.find(L"msgstr") == last_entry.end()) { + errorstream << "Header entry has no \"msgstr\" field" << std::endl; + } else if (plural) { + errorstream << "Attempt to override existing po header entry" << std::endl; + } else { + for (auto &line: str_split(last_entry[L"msgstr"], L'\n')) { + if (str_starts_with(line, L"Plural-Forms:")) { + plural = GettextPluralForm::parseHeaderLine(line); + if (!(plural && *plural)) { + errorstream << "Invalid Plural-Forms line: " << wide_to_utf8(line) << std::endl; + } + } + } + } + } else { + loadPoEntry(wbasefilename, plural, last_entry); + } + } + last_entry.clear(); + skip_last = skip; + } else if (!last_entry.empty()) { + errorstream << "Unable to parse po file: previous entry has no \"msgid\" field but is not empty" << std::endl; + last_entry.clear(); + skip_last = skip; + } + } else { + // prevent malpositioned fuzzy flag from influencing the following entry + skip = false; + } + if (last_entry.find(prefix) != last_entry.end()) { + errorstream << "Unable to parse po file: Key \"" << wide_to_utf8(prefix) << "\" was already present in previous entry" << std::endl; + continue; + } + last_key = prefix; + last_entry[prefix] = s; + } + + if (last_entry.find(L"msgid") != last_entry.end()) { + if (!skip_last && !last_entry[L"msgid"].empty()) + loadPoEntry(wbasefilename, plural, last_entry); + } else if (!last_entry.empty()) { + errorstream << "Unable to parse po file: Last entry has no \"msgid\" field" << std::endl; + } +} + +void Translations::loadMoEntry(const std::wstring &basefilename, const GettextPluralForm::Ptr &plural_form, const std::string &original, const std::string &translated) +{ + std::wstring textdomain = L""; + size_t found; + std::string noriginal = original; + found = original.find('\x04'); // EOT character + if (found != std::string::npos) { + textdomain = utf8_to_wide(original.substr(0, found)); + noriginal = original.substr(found + 1); + } else { + textdomain = basefilename; + } + + found = noriginal.find('\0'); + if (found != std::string::npos) { + std::vector translations = str_split(utf8_to_wide(translated), L'\0'); + addPluralTranslation(textdomain, plural_form, utf8_to_wide(noriginal.substr(0, found)), translations); + addPluralTranslation(textdomain, plural_form, utf8_to_wide(noriginal.substr(found + 1)), translations); + } else { + addTranslation(textdomain, utf8_to_wide(noriginal), utf8_to_wide(translated)); + } +} + +inline u32 readVarEndian(bool is_be, std::string_view data, size_t pos = 0) +{ + if (pos + 4 > data.size()) + return 0; + if (is_be) { + return + ((u32)(unsigned char)data[pos+0] << 24) | ((u32)(unsigned char)data[pos+1] << 16) | + ((u32)(unsigned char)data[pos+2] << 8) | ((u32)(unsigned char)data[pos+3] << 0); + } else { + return + ((u32)(unsigned char)data[pos+0] << 0) | ((u32)(unsigned char)data[pos+1] << 8) | + ((u32)(unsigned char)data[pos+2] << 16) | ((u32)(unsigned char)data[pos+3] << 24); + } +} + +void Translations::loadMoTranslation(const std::string &basefilename, const std::string &data) +{ + size_t length = data.length(); + std::wstring wbasefilename = utf8_to_wide(basefilename); + GettextPluralForm::Ptr plural_form; + + if (length < 20) { + errorstream << "Ignoring too short mo file" << std::endl; + return; + } + + u32 magic = readVarEndian(false, data); + bool is_be; + if (magic == 0x950412de) { + is_be = false; + } else if (magic == 0xde120495) { + is_be = true; + } else { + errorstream << "Bad magic number for mo file: 0x" << hex_encode(data.substr(0, 4)) << std::endl; + return; + } + + u32 revision = readVarEndian(is_be, data, 4); + if (revision != 0) { + errorstream << "Unknown revision " << revision << " for mo file" << std::endl; + return; + } + + u32 nstring = readVarEndian(is_be, data, 8); + u32 original_offset = readVarEndian(is_be, data, 12); + u32 translated_offset = readVarEndian(is_be, data, 16); + + if (length < original_offset + 8 * (u64)nstring || length < translated_offset + 8 * (u64)nstring) { + errorstream << "Ignoring truncated mo file" << std::endl; + return; + } + + for (u32 i = 0; i < nstring; i++) { + u32 original_len = readVarEndian(is_be, data, original_offset + 8 * i); + u32 original_off = readVarEndian(is_be, data, original_offset + 8 * i + 4); + u32 translated_len = readVarEndian(is_be, data, translated_offset + 8 * i); + u32 translated_off = readVarEndian(is_be, data, translated_offset + 8 * i + 4); + + if (length < original_off + (u64)original_len || length < translated_off + (u64)translated_len) { + errorstream << "Ignoring translation out of mo file" << std::endl; + continue; + } + + if (data[original_off+original_len] != '\0' || data[translated_off+translated_len] != '\0') { + errorstream << "String in mo entry does not have a trailing NUL" << std::endl; + continue; + } + + auto original = data.substr(original_off, original_len); + auto translated = data.substr(translated_off, translated_len); + + if (original.empty()) { + if (plural_form) { + errorstream << "Attempt to override existing mo header entry" << std::endl; + } else { + for (auto &line: str_split(translated, '\n')) { + if (str_starts_with(line, "Plural-Forms:")) { + plural_form = GettextPluralForm::parseHeaderLine(utf8_to_wide(line)); + if (!(plural_form && *plural_form)) { + errorstream << "Invalid Plural-Forms line: " << line << std::endl; + } + } + } + } + } else { + loadMoEntry(wbasefilename, plural_form, original, translated); + } + } + + return; +} + +void Translations::loadTranslation(const std::string &filename, const std::string &data) +{ + const char *trExtension[] = { ".tr", NULL }; + const char *poExtension[] = { ".po", NULL }; + const char *moExtension[] = { ".mo", NULL }; + if (!removeStringEnd(filename, trExtension).empty()) { + loadTrTranslation(data); + } else if (!removeStringEnd(filename, poExtension).empty()) { + std::string basefilename = str_split(filename, '.')[0]; + loadPoTranslation(basefilename, data); + } else if (!removeStringEnd(filename, moExtension).empty()) { + std::string basefilename = str_split(filename, '.')[0]; + loadMoTranslation(basefilename, data); + } else { + errorstream << "loadTranslation called with invalid filename: \"" << filename << "\"" << std::endl; } } diff --git a/src/translation.h b/src/translation.h index d7ed15505..972cdafef 100644 --- a/src/translation.h +++ b/src/translation.h @@ -19,8 +19,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "gettext_plural_form.h" #include +#include +#include #include +#include class Translations; #ifndef SERVER @@ -30,11 +34,39 @@ extern Translations *g_client_translations; class Translations { public: - void loadTranslation(const std::string &data); + void loadTranslation(const std::string &filename, const std::string &data); void clear(); - const std::wstring &getTranslation(const std::wstring &textdomain, - const std::wstring &s) const; + const std::wstring &getTranslation( + const std::wstring &textdomain, const std::wstring &s) const; + const std::wstring &getPluralTranslation(const std::wstring &textdomain, + const std::wstring &s, unsigned long int number) const; + static const std::string_view getFileLanguage(const std::string &filename); + static inline bool isTranslationFile(const std::string &filename) + { + return getFileLanguage(filename) != ""; + } + // for testing + inline size_t size() + { + return m_translations.size() + m_plural_translations.size()/2; + } private: std::unordered_map m_translations; + std::unordered_map>> m_plural_translations; + + void addTranslation(const std::wstring &textdomain, const std::wstring &original, + const std::wstring &translated); + void addPluralTranslation(const std::wstring &textdomain, + const GettextPluralForm::Ptr &plural, + const std::wstring &original, + std::vector &translated); + std::wstring unescapeC(const std::wstring &str); + std::optional> parsePoLine(const std::string &line); + bool inEscape(const std::wstring &str, size_t pos); + void loadPoEntry(const std::wstring &basefilename, const GettextPluralForm::Ptr &plural_form, const std::map &entry); + void loadMoEntry(const std::wstring &basefilename, const GettextPluralForm::Ptr &plural_form, const std::string &original, const std::string &translated); + void loadTrTranslation(const std::string &data); + void loadPoTranslation(const std::string &basefilename, const std::string &data); + void loadMoTranslation(const std::string &basefilename, const std::string &data); }; diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index f546d150e..46e4f9a18 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -37,6 +37,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_socket.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_servermodmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_threading.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_translations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_utilities.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_voxelarea.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_voxelalgorithms.cpp diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index 03fdc7042..033cbbc49 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -122,7 +122,7 @@ void TestServerModManager::testGetMods() ServerModManager sm(m_worlddir); const auto &mods = sm.getMods(); // `ls ./games/devtest/mods | wc -l` + 1 (test mod) - UASSERTEQ(std::size_t, mods.size(), 33 + 1); + UASSERTEQ(std::size_t, mods.size(), 34 + 1); // Ensure we found basenodes mod (part of devtest) // and test_mod (for testing MINETEST_MOD_PATH). diff --git a/src/unittest/test_translations.cpp b/src/unittest/test_translations.cpp new file mode 100644 index 000000000..37fc78ee4 --- /dev/null +++ b/src/unittest/test_translations.cpp @@ -0,0 +1,64 @@ +// Minetest +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "translation.h" +#include "filesys.h" +#include "content/subgames.h" +#include "catch.h" + +#define CONTEXT L"context" +#define TEXTDOMAIN_PO L"translation_po" +#define TEST_PO_NAME "translation_po.de.po" +#define TEST_MO_NAME "translation_mo.de.mo" + +static std::string read_translation_file(const std::string &filename) +{ + auto gamespec = findSubgame("devtest"); + REQUIRE(gamespec.isValid()); + auto path = gamespec.gamemods_path + (DIR_DELIM "testtranslations" DIR_DELIM "test_locale" DIR_DELIM) + filename; + std::string content; + REQUIRE(fs::ReadFile(path, content)); + return content; +} + +TEST_CASE("test translations") +{ + SECTION("Plural-Forms function for translations") + { + auto form = GettextPluralForm::parseHeaderLine(L"Plural-Forms: nplurals=3; plural= (n-1+1)<=1 ? n||1?0:1 : 1?(!!2):2;"); + REQUIRE(form); + REQUIRE(form->size() == 3); + CHECK((*form)(0) == 0); + CHECK((*form)(1) == 0); + CHECK((*form)(2) == 1); + } + + SECTION("PO file parser") + { + Translations translations; + translations.loadTranslation(TEST_PO_NAME, read_translation_file(TEST_PO_NAME)); + + CHECK(translations.size() == 5); + CHECK(translations.getTranslation(TEXTDOMAIN_PO, L"foo") == L"bar"); + CHECK(translations.getTranslation(TEXTDOMAIN_PO, L"Untranslated") == L"Untranslated"); + CHECK(translations.getTranslation(TEXTDOMAIN_PO, L"Fuzzy") == L"Fuzzy"); + CHECK(translations.getTranslation(TEXTDOMAIN_PO, L"Multi\\line\nstring") == L"Multi\\\"li\\ne\nresult"); + CHECK(translations.getTranslation(TEXTDOMAIN_PO, L"Wrong order") == L"Wrong order"); + CHECK(translations.getPluralTranslation(TEXTDOMAIN_PO, L"Plural form", 1) == L"Singular result"); + CHECK(translations.getPluralTranslation(TEXTDOMAIN_PO, L"Singular form", 0) == L"Plural result"); + CHECK(translations.getPluralTranslation(TEXTDOMAIN_PO, L"Partial translation", 1) == L"Partially translated"); + CHECK(translations.getPluralTranslation(TEXTDOMAIN_PO, L"Partial translations", 2) == L"Partial translations"); + CHECK(translations.getTranslation(CONTEXT, L"With context") == L"Has context"); + } + + SECTION("MO file parser") + { + Translations translations; + translations.loadTranslation(TEST_MO_NAME, read_translation_file(TEST_MO_NAME)); + + CHECK(translations.size() == 2); + CHECK(translations.getTranslation(CONTEXT, L"With context") == L"Has context"); + CHECK(translations.getPluralTranslation(CONTEXT, L"Plural form", 1) == L"Singular result"); + CHECK(translations.getPluralTranslation(CONTEXT, L"Singular form", 0) == L"Plural result"); + } +} diff --git a/src/util/string.cpp b/src/util/string.cpp index 74a360266..b05d993a5 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -154,6 +154,16 @@ std::string wide_to_utf8(std::wstring_view input) return out; } +void wide_add_codepoint(std::wstring &result, char32_t codepoint) +{ + if ((0xD800 <= codepoint && codepoint <= 0xDFFF) || (0x10FFFF < codepoint)) { + // Invalid codepoint, replace with unicode replacement character + result.push_back(0xFFFD); + return; + } + result.push_back(codepoint); +} + #else // _WIN32 std::wstring utf8_to_wide(std::string_view input) @@ -180,6 +190,29 @@ std::string wide_to_utf8(std::wstring_view input) return out; } +void wide_add_codepoint(std::wstring &result, char32_t codepoint) +{ + if (codepoint < 0x10000) { + if (0xD800 <= codepoint && codepoint <= 0xDFFF) { + // Invalid codepoint, part of a surrogate pair + // Replace with unicode replacement character + result.push_back(0xFFFD); + return; + } + result.push_back((wchar_t) codepoint); + return; + } + codepoint -= 0x10000; + if (codepoint >= 0x100000) { + // original codepoint was above 0x10FFFF, so invalid + // replace with unicode replacement character + result.push_back(0xFFFD); + return; + } + result.push_back((wchar_t) ((codepoint >> 10) | 0xD800)); + result.push_back((wchar_t) ((codepoint & 0x3FF) | 0xDC00)); +} + #endif // _WIN32 @@ -668,13 +701,20 @@ std::string wrap_rows(std::string_view from, unsigned row_len, bool has_color_co * We get the argument "White", translated, and create a template string with "@1" instead of it. * We finally get the template "@1 Wool" that was used in the beginning, which we translate * before filling it again. + * + * The \x1bT marking the beginning of a translated string allows two '@'-separated arguments: + * - The first one is the textdomain/context in which the string is to be translated. Most often, + * this is the name of the mod which asked for the translation. + * - The second argument, if present, should be an integer; it is used to decide which plural form + * to use, for languages containing several plural forms. */ static void translate_all(std::wstring_view s, size_t &i, Translations *translations, std::wstring &res); static void translate_string(std::wstring_view s, Translations *translations, - const std::wstring &textdomain, size_t &i, std::wstring &res) + const std::wstring &textdomain, size_t &i, std::wstring &res, + bool use_plural, unsigned long int number) { std::vector args; int arg_number = 1; @@ -751,8 +791,17 @@ static void translate_string(std::wstring_view s, Translations *translations, } // Translate the template. - const std::wstring &toutput = translations ? - translations->getTranslation(textdomain, output) : output; + std::wstring toutput; + if (translations != nullptr) { + if (use_plural) + toutput = translations->getPluralTranslation( + textdomain, output, number); + else + toutput = translations->getTranslation( + textdomain, output); + } else { + toutput = output; + } // Put back the arguments in the translated template. size_t j = 0; @@ -835,10 +884,37 @@ static void translate_all(std::wstring_view s, size_t &i, } else if (parts[0] == L"T") { // Beginning of translated string. std::wstring textdomain; + bool use_plural = false; + unsigned long int number = 0; if (parts.size() > 1) textdomain = parts[1]; + if (parts.size() > 2 && parts[2] != L"") { + // parts[2] should contain a number used for selecting + // the plural form. + // However, we can't blindly cast it to an unsigned long int, + // as it might be too large for that. + // + // We follow the advice of gettext and reduce integers larger than 1000000 + // to something in the range [1000000, 2000000), with the same last 6 digits. + // + // https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html + constexpr unsigned long int max = 1000000; + + use_plural = true; + number = 0; + for (char c : parts[2]) { + if (L'0' <= c && c <= L'9') { + number = (10 * number + (unsigned long int)(c - L'0')); + if (number >= 2 * max) number = (number % max) + max; + } else { + // Invalid number + use_plural = false; + break; + } + } + } std::wstring translated; - translate_string(s, translations, textdomain, i, translated); + translate_string(s, translations, textdomain, i, translated, use_plural, number); res.append(translated); } else { // Another escape sequence, such as colors. Preserve it. diff --git a/src/util/string.h b/src/util/string.h index aae1167b6..50e208966 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include #include class Translations; @@ -87,6 +88,8 @@ struct FlagDesc { std::wstring utf8_to_wide(std::string_view input); std::string wide_to_utf8(std::wstring_view input); +void wide_add_codepoint(std::wstring &result, char32_t codepoint); + std::string urlencode(std::string_view str); std::string urldecode(std::string_view str); @@ -325,19 +328,30 @@ inline std::string lowercase(std::string_view str) } +inline bool my_isspace(const char c) +{ + return std::isspace(c); +} + +inline bool my_isspace(const wchar_t c) +{ + return std::iswspace(c); +} + /** * @param str * @return A view of \p str with leading and trailing whitespace removed. */ -inline std::string_view trim(std::string_view str) +template +inline std::basic_string_view trim(const std::basic_string_view &str) { size_t front = 0; size_t back = str.size(); - while (front < back && std::isspace(str[front])) + while (front < back && my_isspace(str[front])) ++front; - while (back > front && std::isspace(str[back - 1])) + while (back > front && my_isspace(str[back - 1])) --back; return str.substr(front, back - front); @@ -351,16 +365,24 @@ inline std::string_view trim(std::string_view str) * @param str * @return A copy of \p str with leading and trailing whitespace removed. */ -inline std::string trim(std::string &&str) +template +inline std::basic_string trim(std::basic_string &&str) { - std::string ret(trim(std::string_view(str))); + std::basic_string ret(trim(std::basic_string_view(str))); return ret; } -// The above declaration causes ambiguity with char pointers so we have to fix that: -inline std::string_view trim(const char *str) +template +inline std::basic_string_view trim(const std::basic_string &str) { - return trim(std::string_view(str)); + return trim(std::basic_string_view(str)); +} + +// The above declaration causes ambiguity with char pointers so we have to fix that: +template +inline std::basic_string_view trim(const T *str) +{ + return trim(std::basic_string_view(str)); } From 067a5b5ac3225183ad5b44684cbf3ea101314fa2 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Sun, 13 Oct 2024 15:42:26 +0200 Subject: [PATCH 199/200] Fix local animations not working (was broken in 06907aa) --- src/network/clientpackethandler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 373e39b4e..310e10e6e 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1527,6 +1527,8 @@ void Client::handleCommand_LocalPlayerAnimations(NetworkPacket* pkt) } } + *pkt >> player->local_animation_speed; + player->last_animation = LocalPlayerAnimation::NO_ANIM; } From ecf8488406e8b2bd25aac007856360789f08b101 Mon Sep 17 00:00:00 2001 From: cx384 Date: Sun, 28 Jan 2024 17:35:31 +0100 Subject: [PATCH 200/200] Fix HUD inventory direction position --- games/devtest/mods/testhud/init.lua | 76 +++++++++++++++++++++++++++-- src/client/hud.cpp | 8 +-- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/games/devtest/mods/testhud/init.lua b/games/devtest/mods/testhud/init.lua index 1788ad77e..4afece209 100644 --- a/games/devtest/mods/testhud/init.lua +++ b/games/devtest/mods/testhud/init.lua @@ -272,11 +272,81 @@ minetest.register_chatcommand("hudhotbars", { end }) +-- Inventories + +local hud_inventory_defs = { + { + type = "inventory", + position = {x=0.2, y=0.5}, + direction = 0, + text = "main", + number = 4, + item = 2, + }, + { + type = "inventory", + position = {x=0.2, y=0.5}, + direction = 2, + text = "main", + number = 4, + item = 2, + }, + { + type = "inventory", + position = {x=0.7, y=0.5}, + direction = 1, + text = "main", + number = 4, + item = 2, + }, + { + type = "inventory", + position = {x=0.7, y=0.5}, + direction = 3, + text = "main", + number = 4, + item = 2, + }, +} + +local player_hud_inventories= {} +minetest.register_chatcommand("hudinventories", { + description = "Shows some test Lua HUD elements of type inventory. (add: Adds elements (default). remove: Removes elements)", + params = "[ add | remove ]", + func = function(name, params) + local player = minetest.get_player_by_name(name) + if not player then + return false, "No player." + end + + local id_table = player_hud_inventories[name] + if not id_table then + id_table = {} + player_hud_inventories[name] = id_table + end + + if params == "remove" then + for _, id in ipairs(id_table) do + player:hud_remove(id) + end + return true, "HUD Inventories removed." + end + + -- params == "add" or default + for _, def in ipairs(hud_inventory_defs) do + table.insert(id_table, player:hud_add(def)) + end + return true, #hud_inventory_defs .." HUD Inventories added." + end +}) + minetest.register_on_leaveplayer(function(player) - player_font_huds[player:get_player_name()] = nil - player_waypoints[player:get_player_name()] = nil - player_hud_hotbars[player:get_player_name()] = nil + local playername = player:get_player_name() + player_font_huds[playername] = nil + player_waypoints[playername] = nil + player_hud_hotbars[playername] = nil + player_hud_inventories[playername] = nil end) minetest.register_chatcommand("hudprint", { diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 29fc31ffa..6fd6b7d03 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -302,20 +302,20 @@ void Hud::drawItems(v2s32 screen_pos, v2s32 screen_offset, s32 itemcount, v2f al // Draw items core::rect imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize); - const s32 list_size = mainlist ? mainlist->getSize() : 0; - for (s32 i = inv_offset; i < itemcount && i < list_size; i++) { + const s32 list_max = std::min(itemcount, (s32) (mainlist ? mainlist->getSize() : 0 )); + for (s32 i = inv_offset; i < list_max; i++) { s32 fullimglen = m_hotbar_imagesize + m_padding * 2; v2s32 steppos; switch (direction) { case HUD_DIR_RIGHT_LEFT: - steppos = v2s32(-(m_padding + (i - inv_offset) * fullimglen), m_padding); + steppos = v2s32(m_padding + (list_max - 1 - i - inv_offset) * fullimglen, m_padding); break; case HUD_DIR_TOP_BOTTOM: steppos = v2s32(m_padding, m_padding + (i - inv_offset) * fullimglen); break; case HUD_DIR_BOTTOM_TOP: - steppos = v2s32(m_padding, -(m_padding + (i - inv_offset) * fullimglen)); + steppos = v2s32(m_padding, m_padding + (list_max - 1 - i - inv_offset) * fullimglen); break; default: steppos = v2s32(m_padding + (i - inv_offset) * fullimglen, m_padding);