From dca88be81d23bcbd9e947700fc6c78afe011f1f1 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 18 May 2025 12:13:33 +0200 Subject: [PATCH 01/48] Remove PrefersNonDefaultGPU from desktop file (#16095) --- misc/AppImageBuilder.yml | 2 -- misc/org.luanti.luanti.desktop | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/misc/AppImageBuilder.yml b/misc/AppImageBuilder.yml index 4862e58f1..b69b88a17 100644 --- a/misc/AppImageBuilder.yml +++ b/misc/AppImageBuilder.yml @@ -56,5 +56,3 @@ script: | # Is a backup icon location in case mkdir -p AppDir/usr/share/luanti/misc cp AppDir/usr/share/icons/hicolor/128x128/apps/luanti.png AppDir/usr/share/luanti/misc/luanti-xorg-icon-128.png - # Validation issues - sed -i '/PrefersNonDefaultGPU/d' AppDir/usr/share/applications/org.luanti.luanti.desktop diff --git a/misc/org.luanti.luanti.desktop b/misc/org.luanti.luanti.desktop index 325bd59d6..eab76aabb 100644 --- a/misc/org.luanti.luanti.desktop +++ b/misc/org.luanti.luanti.desktop @@ -7,7 +7,7 @@ Comment[fr]=Plate-forme de jeu multijoueurs à base de blocs Exec=luanti Icon=luanti Terminal=false -PrefersNonDefaultGPU=true +# Note: don't add PrefersNonDefaultGPU here, see #16095 Type=Application Categories=Game;Simulation; StartupNotify=false From 8c8b7cb251d85baf4a55c08dcd0545bc17d7bdc6 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 18 May 2025 12:13:48 +0200 Subject: [PATCH 02/48] Clean up menus properly on client exit (#16150) --- src/client/game.cpp | 11 +++++------ src/client/game_formspec.cpp | 5 +++-- src/client/game_formspec.h | 3 ++- src/gui/mainmenumanager.h | 2 ++ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index f340f98e4..2c9c4fb77 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1114,8 +1114,12 @@ void Game::run() void Game::shutdown() { - // Clear text when exiting. + // Delete text and menus first m_game_ui->clearText(); + m_game_formspec.reset(); + while (g_menumgr.menuCount() > 0) { + g_menumgr.deleteFront(); + } if (g_touchcontrols) g_touchcontrols->hide(); @@ -1126,11 +1130,6 @@ void Game::shutdown() sky.reset(); - /* cleanup menus */ - while (g_menumgr.menuCount() > 0) { - g_menumgr.deleteFront(); - } - // only if the shutdown progress bar isn't shown yet if (m_shutdown_progress == 0.0f) showOverlayMessage(N_("Shutting down..."), 0, 0); diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp index 3d46ddab5..dc51247b0 100644 --- a/src/client/game_formspec.cpp +++ b/src/client/game_formspec.cpp @@ -217,10 +217,11 @@ void GameFormSpec::deleteFormspec() } } -GameFormSpec::~GameFormSpec() { +void GameFormSpec::reset() +{ if (m_formspec) m_formspec->quitMenu(); - this->deleteFormspec(); + deleteFormspec(); } bool GameFormSpec::handleEmptyFormspec(const std::string &formspec, const std::string &formname) diff --git a/src/client/game_formspec.h b/src/client/game_formspec.h index 6dff32e50..980dac47f 100644 --- a/src/client/game_formspec.h +++ b/src/client/game_formspec.h @@ -26,7 +26,7 @@ struct GameFormSpec { void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input); - ~GameFormSpec(); + ~GameFormSpec() { reset(); } void showFormSpec(const std::string &formspec, const std::string &formname); void showCSMFormSpec(const std::string &formspec, const std::string &formname); @@ -43,6 +43,7 @@ struct GameFormSpec void disableDebugView(); bool handleCallbacks(); + void reset(); #ifdef __ANDROID__ // Returns false if no formspec open diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 3bc8f7daa..553d6ffce 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -59,6 +59,8 @@ public: if(!m_stack.empty()) { m_stack.back()->setVisible(true); guienv->setFocus(m_stack.back()); + } else { + guienv->removeFocus(menu); } } From 554dd5ddf4b800a9a891f29efeb522f8c68807c4 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 18 May 2025 12:13:57 +0200 Subject: [PATCH 03/48] Update credits for 5.12.0 (#16142) --- builtin/mainmenu/credits.json | 15 +++++++-------- util/gather_git_credits.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/builtin/mainmenu/credits.json b/builtin/mainmenu/credits.json index bd4f0591f..61e53d3ba 100644 --- a/builtin/mainmenu/credits.json +++ b/builtin/mainmenu/credits.json @@ -47,22 +47,20 @@ ], "#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py", "contributors": [ - "JosiahWI", "Erich Schubert", "wrrrzr", - "1F616EMO", - "red-001 ", - "veprogames", - "paradust7", - "AFCMS", "siliconsniffer", - "Wuzzy", - "Zemtzov7" + "JosiahWI", + "veprogames", + "Miguel P.L", + "AFCMS" ], "previous_contributors": [ "Ælla Chiana Moskopp (erle) [Logo]", "numzero", + "red-001 ", "Giuseppe Bilotta", + "HybridDog", "ClobberXD", "Dániel Juhász (juhdanad) ", "MirceaKitsune ", @@ -75,6 +73,7 @@ "stujones11", "Rogier ", "Gregory Currie (gregorycu)", + "paradust7", "JacobF", "Jeija " ] diff --git a/util/gather_git_credits.py b/util/gather_git_credits.py index b02275644..c415e4263 100755 --- a/util/gather_git_credits.py +++ b/util/gather_git_credits.py @@ -6,7 +6,7 @@ from collections import defaultdict codefiles = r"(\.[ch](pp)?|\.lua|\.md|\.cmake|\.java|\.gradle|Makefile|CMakeLists\.txt)$" # two minor versions back, for "Active Contributors" -REVS_ACTIVE = "5.9.0..HEAD" +REVS_ACTIVE = "5.10.0..HEAD" # all time, for "Previous Contributors" REVS_PREVIOUS = "HEAD" From 56ecf6d332ac3feab562b4f60bf38b0d677f594f Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 18 May 2025 20:41:42 +0200 Subject: [PATCH 04/48] Mainmenu: Fix error after ESC in dialog windows (#16130) The error was caused by fd857374, where 'MenuQuit' was processed after 'try_quit'. This commit fixes the error by moving the special 'MenuQuit' handling to Lua. --- builtin/fstk/ui.lua | 4 ++++ src/gui/guiFormSpecMenu.cpp | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua index 47b9d086c..44d2ab3d8 100644 --- a/builtin/fstk/ui.lua +++ b/builtin/fstk/ui.lua @@ -166,6 +166,10 @@ end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- core.button_handler = function(fields) + if fields["try_quit"] and not fields["key_enter"] then + core.event_handler("MenuQuit") + return + end if fields["btn_reconnect_yes"] then gamedata.reconnect_requested = false gamedata.errormessage = nil diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index cee066131..4b9601430 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -4053,7 +4053,6 @@ void GUIFormSpecMenu::tryClose() quitMenu(); } else { acceptInput(quit_mode_try); - m_text_dst->gotText(L"MenuQuit"); } } From 4700939949fcd4334475a7fd151c4776fe566629 Mon Sep 17 00:00:00 2001 From: Daniel Cristian Date: Sun, 18 May 2025 16:59:57 -0300 Subject: [PATCH 05/48] Fix uninitialized variable warning in generate_srp_verifier_and_salt --- src/util/auth.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/auth.cpp b/src/util/auth.cpp index 040d15bf9..5116e4be7 100644 --- a/src/util/auth.cpp +++ b/src/util/auth.cpp @@ -67,9 +67,9 @@ void generate_srp_verifier_and_salt(const std::string &name, std::string *salt) { char *bytes_v = nullptr; - size_t verifier_len; + size_t verifier_len = 0; char *salt_ptr = nullptr; - size_t salt_len; + size_t salt_len = 0; gen_srp_v(name, password, &salt_ptr, &salt_len, &bytes_v, &verifier_len); *verifier = std::string(bytes_v, verifier_len); *salt = std::string(salt_ptr, salt_len); From 30e33d71cc761ebb77ac5716a75de61677a6627b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathana=C3=ABlle=20Courant?= Date: Mon, 19 May 2025 10:29:37 +0200 Subject: [PATCH 06/48] Main menu: Fix ContentDB aliases for games having the '_game' suffix (#16157) --- builtin/mainmenu/content/contentdb.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/builtin/mainmenu/content/contentdb.lua b/builtin/mainmenu/content/contentdb.lua index be148841a..fbd94376d 100644 --- a/builtin/mainmenu/content/contentdb.lua +++ b/builtin/mainmenu/content/contentdb.lua @@ -170,14 +170,16 @@ function contentdb.get_package_by_id(id) end -function contentdb.calculate_package_id(type, author, name) - local id = author:lower() .. "/" +local function strip_game_suffix(type, name) if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then - id = id .. name:sub(1, #name - 5) + return name:sub(1, #name - 5) else - id = id .. name + return name end - return id +end + +function contentdb.calculate_package_id(type, author, name) + return author:lower() .. "/" .. strip_game_suffix(type, name) end @@ -427,7 +429,7 @@ function contentdb.set_packages_from_api(packages) -- We currently don't support name changing local suffix = "/" .. package.name if alias:sub(-#suffix) == suffix then - contentdb.aliases[alias:lower()] = package.id + contentdb.aliases[strip_game_suffix(packages.type, alias:lower())] = package.id end end end From 7ac5502fdf23263ad6b424c2da6c18ffff122df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Tue, 20 May 2025 18:37:33 +0200 Subject: [PATCH 07/48] Fix handling of skinned meshes for nodes Second try after the revert in 8a28339 due to an unexpected regression. - Rigidly animated models (e.g. the glTF frog node) were not working correctly, since cloning the mesh ignored the transformation matrices. Note that scaling the mesh needs to occur *after* transforming the vertices. - Visual scale did not apply to skinned models, as resetting the animation overwrote scaled vertex data with static positions & normals. For backwards compatibility, we now apply a 10x scale to static, non-glTF models. We now do scale static meshes, as the bug that caused meshes not to be scaled was limited to skeletally animated meshes, hence we ought not to reproduce it for skinned meshes that do not take advantage of skeletal animations (e.g. current MTG doors). However, glTF models (e.g. Wuzzy's eyeballs) up until recently were always affected due to technical reasons (using skeletal animation for rigid animation). Thus, to preserve behavior, we: 1. Do not apply 10x scale to glTF models. 2. Apply 10x scale to obj models. 3. Apply 10x scale to static x or b3d models, but not to animated ones. See also: #16141 --- doc/lua_api.md | 9 ++-- irr/include/SkinnedMesh.h | 21 ++++++-- irr/src/CB3DMeshFileLoader.cpp | 2 +- irr/src/CGLTFMeshFileLoader.cpp | 3 +- irr/src/CSceneManager.cpp | 2 +- irr/src/CXMeshFileLoader.cpp | 2 +- src/client/content_mapblock.cpp | 2 +- src/client/mesh.cpp | 95 +++++++++++++++++---------------- src/client/mesh.h | 6 +-- src/client/wieldmesh.cpp | 4 +- src/nodedef.cpp | 45 ++++++++++++---- src/nodedef.h | 2 +- 12 files changed, 118 insertions(+), 75 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index f028a14d8..b604b317c 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -10172,9 +10172,12 @@ Used by `core.register_node`. mesh = "", -- File name of mesh when using "mesh" drawtype -- The center of the node is the model origin. - -- For legacy reasons, models in OBJ format use a scale of 1 node = 1 unit; - -- all other model file formats use a scale of 1 node = 10 units, - -- consistent with the scale used for entities. + -- For legacy reasons, this uses a different scale depending on the mesh: + -- 1. For glTF models: 10 units = 1 node (consistent with the scale for entities). + -- 2. For obj models: 1 unit = 1 node. + -- 3. For b3d and x models: 1 unit = 1 node if static, otherwise 10 units = 1 node. + -- Using static glTF or obj models is recommended. + -- You can use the `visual_scale` multiplier to achieve the expected scale. selection_box = { -- see [Node boxes] for possibilities diff --git a/irr/include/SkinnedMesh.h b/irr/include/SkinnedMesh.h index c9ea99365..a527db76e 100644 --- a/irr/include/SkinnedMesh.h +++ b/irr/include/SkinnedMesh.h @@ -26,12 +26,21 @@ class ISceneManager; class SkinnedMesh : public IAnimatedMesh { public: + + enum class SourceFormat { + B3D, + X, + GLTF, + OTHER, + }; + //! constructor - SkinnedMesh() : + SkinnedMesh(SourceFormat src_format) : EndFrame(0.f), FramesPerSecond(25.f), LastAnimatedFrame(-1), SkinnedLastFrame(false), HasAnimation(false), PreparedForSkinning(false), - AnimateNormals(true), HardwareSkinning(false) + AnimateNormals(true), HardwareSkinning(false), + SrcFormat(src_format) { SkinningBuffers = &LocalBuffers; } @@ -39,6 +48,10 @@ public: //! destructor virtual ~SkinnedMesh(); + //! The source (file) format the mesh was loaded from. + //! Important for legacy reasons pertaining to different mesh loader behavior. + SourceFormat getSourceFormat() const { return SrcFormat; } + //! If the duration is 0, it is a static (=non animated) mesh. f32 getMaxFrameNumber() const override; @@ -382,12 +395,14 @@ protected: bool PreparedForSkinning; bool AnimateNormals; bool HardwareSkinning; + + SourceFormat SrcFormat; }; // Interface for mesh loaders class SkinnedMeshBuilder : public SkinnedMesh { public: - SkinnedMeshBuilder() : SkinnedMesh() {} + SkinnedMeshBuilder(SourceFormat src_format) : SkinnedMesh(src_format) {} //! loaders should call this after populating the mesh // returns *this, so do not try to drop the mesh builder instance diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index e99bd2eed..51342f451 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -48,7 +48,7 @@ IAnimatedMesh *CB3DMeshFileLoader::createMesh(io::IReadFile *file) return 0; B3DFile = file; - AnimatedMesh = new scene::SkinnedMeshBuilder(); + AnimatedMesh = new scene::SkinnedMeshBuilder(SkinnedMesh::SourceFormat::B3D); ShowWarning = true; // If true a warning is issued if too many textures are used VerticesStart = 0; diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index f70f6692b..3f2096f40 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -347,7 +347,8 @@ IAnimatedMesh* SelfType::createMesh(io::IReadFile* file) const char *filename = file->getFileName().c_str(); try { tiniergltf::GlTF model = parseGLTF(file); - irr_ptr mesh(new SkinnedMeshBuilder()); + irr_ptr mesh(new SkinnedMeshBuilder( + SkinnedMesh::SourceFormat::GLTF)); MeshExtractor extractor(std::move(model), mesh.get()); try { extractor.load(); diff --git a/irr/src/CSceneManager.cpp b/irr/src/CSceneManager.cpp index b5a310287..05f8de71a 100644 --- a/irr/src/CSceneManager.cpp +++ b/irr/src/CSceneManager.cpp @@ -762,7 +762,7 @@ ISceneManager *CSceneManager::createNewSceneManager(bool cloneContent) //! Get a skinned mesh, which is not available as header-only code SkinnedMesh *CSceneManager::createSkinnedMesh() { - return new SkinnedMesh(); + return new SkinnedMesh(SkinnedMesh::SourceFormat::OTHER); } // creates a scenemanager diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index ed8c18350..d93502d6b 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -54,7 +54,7 @@ IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file) u32 time = os::Timer::getRealTime(); #endif - AnimatedMesh = new SkinnedMeshBuilder(); + AnimatedMesh = new SkinnedMeshBuilder(SkinnedMesh::SourceFormat::X); SkinnedMesh *res = nullptr; if (load(file)) { diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index f5b528287..3edba95e3 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -1676,7 +1676,7 @@ void MapblockMeshGenerator::drawMeshNode() if (cur_node.f->mesh_ptr) { // clone and rotate mesh - mesh = cloneMesh(cur_node.f->mesh_ptr); + mesh = cloneStaticMesh(cur_node.f->mesh_ptr); bool modified = true; if (facedir) rotateMeshBy6dFacedir(mesh, facedir); diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index a2c0ae327..808dcdd18 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -3,6 +3,8 @@ // Copyright (C) 2010-2013 celeron55, Perttu Ahola #include "mesh.h" +#include "IMeshBuffer.h" +#include "SSkinMeshBuffer.h" #include "debug.h" #include "log.h" #include @@ -102,6 +104,21 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale) return anim_mesh; } +template +inline static void transformMeshBuffer(scene::IMeshBuffer *buf, + const F &transform_vertex) +{ + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) { + auto *vertex = (video::S3DVertex *)(vertices + i * stride); + transform_vertex(vertex); + } + buf->setDirty(scene::EBT_VERTEX); + buf->recalculateBoundingBox(); +} + void scaleMesh(scene::IMesh *mesh, v3f scale) { if (mesh == NULL) @@ -112,14 +129,9 @@ void scaleMesh(scene::IMesh *mesh, v3f scale) u32 mc = mesh->getMeshBufferCount(); for (u32 j = 0; j < mc; j++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - 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))->Pos *= scale; - - buf->setDirty(scene::EBT_VERTEX); - buf->recalculateBoundingBox(); + transformMeshBuffer(buf, [scale](video::S3DVertex *vertex) { + vertex->Pos *= scale; + }); // calculate total bounding box if (j == 0) @@ -140,14 +152,9 @@ void translateMesh(scene::IMesh *mesh, v3f vec) u32 mc = mesh->getMeshBufferCount(); for (u32 j = 0; j < mc; j++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - 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))->Pos += vec; - - buf->setDirty(scene::EBT_VERTEX); - buf->recalculateBoundingBox(); + transformMeshBuffer(buf, [vec](video::S3DVertex *vertex) { + vertex->Pos += vec; + }); // calculate total bounding box if (j == 0) @@ -330,44 +337,40 @@ bool checkMeshNormals(scene::IMesh *mesh) return true; } +template +static scene::IMeshBuffer *cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) +{ + auto *v = static_cast(mesh_buffer->getVertices()); + u16 *indices = mesh_buffer->getIndices(); + auto *cloned_buffer = new SMeshBufferType(); + cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, + mesh_buffer->getIndexCount()); + // Rigidly animated meshes may have transformation matrices that need to be applied + if (auto *sbuf = dynamic_cast(mesh_buffer)) { + transformMeshBuffer(cloned_buffer, [sbuf](video::S3DVertex *vertex) { + sbuf->Transformation.transformVect(vertex->Pos); + vertex->Normal = sbuf->Transformation.rotateAndScaleVect(vertex->Normal); + vertex->Normal.normalize(); + }); + } + return cloned_buffer; +} + scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) { switch (mesh_buffer->getVertexType()) { - case video::EVT_STANDARD: { - video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices(); - u16 *indices = mesh_buffer->getIndices(); - scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer(); - cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); - return cloned_buffer; + case video::EVT_STANDARD: + return cloneMeshBuffer(mesh_buffer); + case video::EVT_2TCOORDS: + return cloneMeshBuffer(mesh_buffer); + case video::EVT_TANGENTS: + return cloneMeshBuffer(mesh_buffer); } - case video::EVT_2TCOORDS: { - video::S3DVertex2TCoords *v = - (video::S3DVertex2TCoords *) mesh_buffer->getVertices(); - u16 *indices = mesh_buffer->getIndices(); - scene::SMeshBufferLightMap *cloned_buffer = - new scene::SMeshBufferLightMap(); - cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); - return cloned_buffer; - } - case video::EVT_TANGENTS: { - video::S3DVertexTangents *v = - (video::S3DVertexTangents *) mesh_buffer->getVertices(); - u16 *indices = mesh_buffer->getIndices(); - scene::SMeshBufferTangents *cloned_buffer = - new scene::SMeshBufferTangents(); - cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); - return cloned_buffer; - } - } - // This should not happen. sanity_check(false); return NULL; } -scene::SMesh* cloneMesh(scene::IMesh *src_mesh) +scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh) { scene::SMesh* dst_mesh = new scene::SMesh(); for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { diff --git a/src/client/mesh.h b/src/client/mesh.h index d8eb6080e..53c54fc51 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -93,10 +93,8 @@ void rotateMeshYZby (scene::IMesh *mesh, f64 degrees); */ scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer); -/* - Clone the mesh. -*/ -scene::SMesh* cloneMesh(scene::IMesh *src_mesh); +/// Clone a mesh. For an animated mesh, this will clone the static pose. +scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh); /* Convert nodeboxes to mesh. Each tile goes into a different buffer. diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index baa72f1d9..bdd24a727 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -255,7 +255,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, dim = core::dimension2d(dim.Width, frame_height); } scene::IMesh *original = g_extrusion_mesh_cache->create(dim); - scene::SMesh *mesh = cloneMesh(original); + scene::SMesh *mesh = cloneStaticMesh(original); original->drop(); //set texture mesh->getMeshBuffer(0)->getMaterial().setTexture(0, @@ -639,7 +639,7 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, // get mesh core::dimension2d dim = texture->getSize(); scene::IMesh *original = g_extrusion_mesh_cache->create(dim); - scene::SMesh *mesh = cloneMesh(original); + scene::SMesh *mesh = cloneStaticMesh(original); original->drop(); //set texture diff --git a/src/nodedef.cpp b/src/nodedef.cpp index d4dc16a61..04f1959c1 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -4,6 +4,7 @@ #include "nodedef.h" +#include "SAnimatedMesh.h" #include "itemdef.h" #if CHECK_CLIENT_BUILD() #include "client/mesh.h" @@ -13,6 +14,7 @@ #include "client/texturesource.h" #include "client/tile.h" #include +#include #include #endif #include "log.h" @@ -959,23 +961,44 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc palette = tsrc->getPalette(palette_name); if (drawtype == NDT_MESH && !mesh.empty()) { - // Read the mesh and apply scale - mesh_ptr = client->getMesh(mesh); - if (mesh_ptr) { - v3f scale = v3f(BS) * visual_scale; - scaleMesh(mesh_ptr, scale); + // Note: By freshly reading, we get an unencumbered mesh. + if (scene::IMesh *src_mesh = client->getMesh(mesh)) { + bool apply_bs = false; + // For frame-animated meshes, always get the first frame, + // which holds a model for which we can eventually get the static pose. + while (auto *src_meshes = dynamic_cast(src_mesh)) { + src_mesh = src_meshes->getMesh(0.0f); + src_mesh->grab(); + src_meshes->drop(); + } + if (auto *skinned_mesh = dynamic_cast(src_mesh)) { + // Compatibility: Animated meshes, as well as static gltf meshes, are not scaled by BS. + // See https://github.com/luanti-org/luanti/pull/16112#issuecomment-2881860329 + bool is_gltf = skinned_mesh->getSourceFormat() == + scene::SkinnedMesh::SourceFormat::GLTF; + apply_bs = skinned_mesh->isStatic() && !is_gltf; + // Nodes do not support mesh animation, so we clone the static pose. + // This simplifies working with the mesh: We can just scale the vertices + // as transformations have already been applied. + mesh_ptr = cloneStaticMesh(src_mesh); + src_mesh->drop(); + } else { + auto *static_mesh = dynamic_cast(src_mesh); + assert(static_mesh); + mesh_ptr = static_mesh; + // Compatibility: Apply BS scaling to static meshes (.obj). See #15811. + apply_bs = true; + } + scaleMesh(mesh_ptr, v3f((apply_bs ? BS : 1.0f) * visual_scale)); recalculateBoundingBox(mesh_ptr); if (!checkMeshNormals(mesh_ptr)) { + // TODO this should be done consistently when the mesh is loaded infostream << "ContentFeatures: recalculating normals for mesh " << mesh << std::endl; meshmanip->recalculateNormals(mesh_ptr, true, false); - } else { - // Animation is not supported, but we need to reset it to - // default state if it is animated. - // Note: recalculateNormals() also does this hence the else-block - if (mesh_ptr->getMeshType() == scene::EAMT_SKINNED) - ((scene::SkinnedMesh*) mesh_ptr)->resetAnimation(); } + } else { + mesh_ptr = nullptr; } } } diff --git a/src/nodedef.h b/src/nodedef.h index 71a61896b..967e3fcd4 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -342,7 +342,7 @@ struct ContentFeatures enum NodeDrawType drawtype; std::string mesh; #if CHECK_CLIENT_BUILD() - scene::IMesh *mesh_ptr; // mesh in case of mesh node + scene::SMesh *mesh_ptr; // mesh in case of mesh node video::SColor minimap_color; #endif float visual_scale; // Misc. scale parameter From 95695f1cd2bb6c5bb331c25adce0b381f24039ed Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 14 May 2025 23:49:26 +0200 Subject: [PATCH 08/48] Translated using Weblate (German) Currently translated at 99.8% (1528 of 1531 strings) --- po/de/luanti.po | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/po/de/luanti.po b/po/de/luanti.po index d8cba07fb..2f37c2ec7 100644 --- a/po/de/luanti.po +++ b/po/de/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: German (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-04-25 19:50+0000\n" -"Last-Translator: Wuzzy \n" +"PO-Revision-Date: 2025-05-16 10:31+0000\n" +"Last-Translator: sfan5 \n" "Language-Team: German \n" "Language: de\n" @@ -937,29 +937,27 @@ msgstr "Die Welt „$1“ löschen?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Aufgrund dessen könnte sich Ihre Tastenbelegung geändert haben." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Schauen Sie sich die Einstellungen oder die Dokumentation an:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Schließen" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Tastenbelegung" +msgstr "Geänderte Tastenbelegung" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Einstellungen" +msgstr "Einstellungen öffnen" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "Das Steuerungssystem wurde in Luanti 5.12.0 überarbeitet." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" @@ -4660,7 +4658,7 @@ msgid "" "Instrument global callback functions on registration.\n" "(anything you pass to a core.register_*() function)" msgstr "" -"Globale Rückruffunktionen bei ihrer Registrierung instrumentieren\n" +"Globale Rückruffunktionen bei ihrer Registrierung instrumentieren.\n" "(alles, was man einer Funktion wie core.register_*() übergibt)." #: src/settings_translation_file.cpp From 31e923e51a85c4f4aa1e4400ef2e57c5387d1ba0 Mon Sep 17 00:00:00 2001 From: Francesco Rossi Date: Thu, 15 May 2025 00:01:39 +0200 Subject: [PATCH 09/48] Translated using Weblate (Italian) Currently translated at 76.0% (1164 of 1531 strings) --- po/it/luanti.po | 783 ++++++++++++++++++++++-------------------------- 1 file changed, 366 insertions(+), 417 deletions(-) diff --git a/po/it/luanti.po b/po/it/luanti.po index 6377cca82..cbbbd78cc 100644 --- a/po/it/luanti.po +++ b/po/it/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: Italian (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2024-01-18 07:31+0000\n" -"Last-Translator: Filippo Alfieri \n" +"PO-Revision-Date: 2025-05-23 15:04+0000\n" +"Last-Translator: Francesco Rossi \n" "Language-Team: Italian \n" "Language: it\n" @@ -12,7 +12,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.4-dev\n" +"X-Generator: Weblate 5.12-dev\n" #: builtin/client/chatcommands.lua msgid "Clear the out chat queue" @@ -85,16 +85,15 @@ msgstr "Esplora" #: builtin/common/settings/components.lua msgid "Conflicts with \"$1\"" -msgstr "" +msgstr "Va in conflitto con \"$1\"" #: builtin/common/settings/components.lua msgid "Edit" msgstr "Modifica" #: builtin/common/settings/components.lua -#, fuzzy msgid "Remove keybinding" -msgstr "Mappatura dei tasti." +msgstr "Rimuovi questa mappatura." #: builtin/common/settings/components.lua msgid "Select directory" @@ -219,7 +218,7 @@ msgstr "Accessibilità" #: builtin/common/settings/dlg_settings.lua msgid "Auto" -msgstr "" +msgstr "Automatico" #: builtin/common/settings/dlg_settings.lua #: builtin/mainmenu/content/dlg_contentdb.lua @@ -229,7 +228,7 @@ msgstr "Indietro" #: builtin/common/settings/dlg_settings.lua msgid "Buttons with crosshair" -msgstr "" +msgstr "Pulsanti e mirino" #: builtin/common/settings/dlg_settings.lua src/gui/touchscreenlayout.cpp #: src/settings_translation_file.cpp @@ -259,7 +258,7 @@ msgstr "Generale" #: builtin/common/settings/dlg_settings.lua msgid "Long tap" -msgstr "" +msgstr "Tocco prolungato" #: builtin/common/settings/dlg_settings.lua msgid "Movement" @@ -284,7 +283,7 @@ msgstr "Ricerca" #: builtin/common/settings/dlg_settings.lua msgid "Short tap" -msgstr "" +msgstr "Tocco rapido" #: builtin/common/settings/dlg_settings.lua msgid "Show advanced settings" @@ -301,7 +300,7 @@ msgstr "Tab" #: builtin/common/settings/dlg_settings.lua msgid "Tap with crosshair" -msgstr "" +msgstr "Tocco con mirino" #: builtin/common/settings/dlg_settings.lua #, fuzzy @@ -436,7 +435,7 @@ msgstr "Scaricando $1..." #: builtin/mainmenu/content/dlg_contentdb.lua msgid "All" -msgstr "" +msgstr "Tutto" #: builtin/mainmenu/content/dlg_contentdb.lua #, fuzzy @@ -449,7 +448,7 @@ msgstr "Scaricamento..." #: builtin/mainmenu/content/dlg_contentdb.lua msgid "Featured" -msgstr "" +msgstr "In evidenza" #: builtin/mainmenu/content/dlg_contentdb.lua msgid "Games" @@ -522,7 +521,7 @@ msgstr "Dipendenze:" #: builtin/mainmenu/content/dlg_install.lua msgid "Error getting dependencies for package $1" -msgstr "" +msgstr "Errore nell'ottenere le dipendenze per il pacchetto $1" #: builtin/mainmenu/content/dlg_install.lua msgid "Install" @@ -546,7 +545,7 @@ msgstr "Per favore, controlla che il gioco di base sia corretto." #: builtin/mainmenu/content/dlg_install.lua msgid "You need to install a game before you can install a mod" -msgstr "Devi installare un gioco prima di poter installare un mod" +msgstr "Devi installare un gioco prima di poter installare un modulo" #: builtin/mainmenu/content/dlg_overwrite.lua msgid "\"$1\" already exists. Would you like to overwrite it?" @@ -568,20 +567,19 @@ msgstr "Descrizione del Server" #: builtin/mainmenu/content/dlg_package.lua msgid "Donate" -msgstr "" +msgstr "Dona" #: builtin/mainmenu/content/dlg_package.lua msgid "Error loading package information" -msgstr "" +msgstr "Errore nel caricare le informazioni del pacchetto" #: builtin/mainmenu/content/dlg_package.lua -#, fuzzy msgid "Error loading reviews" -msgstr "Errore di creazione del client: %s" +msgstr "Errore nel caricare le recensioni" #: builtin/mainmenu/content/dlg_package.lua msgid "Forum Topic" -msgstr "" +msgstr "Discussione sul forum" #: builtin/mainmenu/content/dlg_package.lua #, fuzzy @@ -595,19 +593,19 @@ msgstr "Installa $1" #: builtin/mainmenu/content/dlg_package.lua msgid "Issue Tracker" -msgstr "" +msgstr "Segnalazioni" #: builtin/mainmenu/content/dlg_package.lua msgid "Reviews" -msgstr "" +msgstr "Recensioni" #: builtin/mainmenu/content/dlg_package.lua msgid "Source" -msgstr "" +msgstr "Sorgente" #: builtin/mainmenu/content/dlg_package.lua msgid "Translate" -msgstr "" +msgstr "Traduci" #: builtin/mainmenu/content/dlg_package.lua builtin/mainmenu/tab_content.lua msgid "Uninstall" @@ -624,7 +622,7 @@ msgstr "Visita il sito Web" #: builtin/mainmenu/content/dlg_package.lua msgid "by $1 — $2 downloads — +$3 / $4 / -$5" -msgstr "" +msgstr "di $1 — $2 scaricamenti — +$3 / $4 / -$5" #: builtin/mainmenu/content/pkgmgr.lua msgid "$1 (Enabled)" @@ -632,7 +630,7 @@ msgstr "$1 (Attivato)" #: builtin/mainmenu/content/pkgmgr.lua msgid "$1 mods" -msgstr "$1 mod" +msgstr "$1 moduli" #: builtin/mainmenu/content/pkgmgr.lua msgid "Failed to install $1 to $2" @@ -646,11 +644,11 @@ msgstr "" #: builtin/mainmenu/content/pkgmgr.lua msgid "Unable to find a valid mod, modpack, or game" -msgstr "Impossibile trovare un mod o un pacchetto mod validi" +msgstr "Impossibile trovare un modulo o un pacchetto moduli validi" #: builtin/mainmenu/content/pkgmgr.lua msgid "Unable to install a $1 as a $2" -msgstr "Impossibile installare una mod come un $1" +msgstr "Impossibile installare $1 come se fosse $2" #: builtin/mainmenu/content/pkgmgr.lua msgid "Unable to install a $1 as a texture pack" @@ -661,6 +659,8 @@ msgid "" "Players connected to\n" "$1" msgstr "" +"Giocanti connessɜ a\n" +"$1" #: builtin/mainmenu/dlg_config_world.lua msgid "(Enabled, has error)" @@ -676,7 +676,7 @@ msgstr "Disattiva tutto" #: builtin/mainmenu/dlg_config_world.lua msgid "Disable modpack" -msgstr "Disattiva pacchetto mod" +msgstr "Disattiva pacchetto moduli" #: builtin/mainmenu/dlg_config_world.lua msgid "Enable all" @@ -684,14 +684,14 @@ msgstr "Attiva tutto" #: builtin/mainmenu/dlg_config_world.lua msgid "Enable modpack" -msgstr "Attiva pacchetto mod" +msgstr "Attiva pacchetto moduli" #: builtin/mainmenu/dlg_config_world.lua msgid "" "Failed to enable mod \"$1\" as it contains disallowed characters. Only " "characters [a-z0-9_] are allowed." msgstr "" -"Impossibile abilitare la mod \"$1\" poiché contiene caratteri non " +"Impossibile abilitare il modulo \"$1\" poiché contiene caratteri non " "consentiti. Sono ammessi solo caratteri [a-z0-9_]." #: builtin/mainmenu/dlg_config_world.lua @@ -716,7 +716,7 @@ msgstr "Nessuna dipendenza necessaria" #: builtin/mainmenu/dlg_config_world.lua msgid "No modpack description provided." -msgstr "Non è stata fornita alcuna descrizione per il pacchetto mod." +msgstr "Non è stata fornita alcuna descrizione per il pacchetto moduli." #: builtin/mainmenu/dlg_config_world.lua msgid "No optional dependencies" @@ -776,7 +776,7 @@ msgstr "Decorazioni" #: builtin/mainmenu/dlg_create_world.lua msgid "Desert temples" -msgstr "" +msgstr "Templi del deserto" #: builtin/mainmenu/dlg_create_world.lua msgid "Development Test is meant for developers." @@ -947,20 +947,19 @@ msgstr "Eliminare il mondo \"$1\"?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Di conseguenza, la mappatura dei tuoi tasti potrebbe essere cambiata." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Dài un occhio alle impostazioni dei tasti o consulta la documentazione:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Chiudi" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Mappatura dei tasti." +msgstr "Mappatura dei tasti cambiata" #: builtin/mainmenu/dlg_rebind_keys.lua #, fuzzy @@ -970,6 +969,7 @@ msgstr "Impostazioni" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." msgstr "" +"Il sistema di gestione degli input è stato rivisitato nella 5.12.0 di Luanti." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" @@ -1038,14 +1038,14 @@ msgstr "Conferma" #: builtin/mainmenu/dlg_rename_modpack.lua msgid "Rename Modpack:" -msgstr "Rinomina il pacchetto mod:" +msgstr "Rinomina il pacchetto moduli:" #: builtin/mainmenu/dlg_rename_modpack.lua msgid "" "This modpack has an explicit name given in its modpack.conf which will " "override any renaming here." msgstr "" -"Questo pacchetto mod esplicita un nome fornito in modpack.conf che " +"Questo pacchetto moduli esplicita un nome fornito in modpack.conf che " "sovrascriverà ogni modifica del nome qui effettuata." #: builtin/mainmenu/dlg_server_list_mods.lua @@ -1055,15 +1055,15 @@ msgstr "Attiva tutto" #: builtin/mainmenu/dlg_server_list_mods.lua msgid "Group by prefix" -msgstr "" +msgstr "Raggruppa per prefisso" #: builtin/mainmenu/dlg_server_list_mods.lua msgid "The $1 server uses a game called $2 and the following mods:" -msgstr "" +msgstr "Il server $1 usa un gioco chiamato $2 e i seguenti moduli:" #: builtin/mainmenu/dlg_server_list_mods.lua msgid "The $1 server uses the following mods:" -msgstr "" +msgstr "Il server $1 usa i seguenti moduli:" #: builtin/mainmenu/dlg_version_info.lua msgid "A new $1 version is available" @@ -1136,7 +1136,7 @@ msgid "" "Opens the directory that contains user-provided worlds, games, mods,\n" "and texture packs in a file manager / explorer." msgstr "" -"Apre la cartella contenente mondi, giochi, mod e pacchetti\n" +"Apre la cartella contenente mondi, giochi, moduli e pacchetti\n" "texture forniti dall'utente in un gestore / visualizzatore di file ." #: builtin/mainmenu/tab_about.lua @@ -1228,16 +1228,16 @@ msgid "Install games from ContentDB" msgstr "Installa giochi da ContentDB" #: builtin/mainmenu/tab_local.lua -#, fuzzy msgid "Luanti doesn't come with a game by default." -msgstr "" -"Il gioco Minetest Game non è più installato per impostazione predefinita" +msgstr "Di base, Luanti non viene fornito con giochi già installati." #: builtin/mainmenu/tab_local.lua msgid "" "Luanti is a game-creation platform that allows you to play many different " "games." msgstr "" +"Luanti è una piattaforma di gioco che permette di giocare a tanti titoli " +"differenti." #: builtin/mainmenu/tab_local.lua msgid "New" @@ -1272,9 +1272,8 @@ msgid "Start Game" msgstr "Gioca" #: builtin/mainmenu/tab_local.lua -#, fuzzy msgid "You need to install a game before you can create a world." -msgstr "Devi installare un gioco prima di poter installare un mod" +msgstr "Devi installare un gioco prima di poter creare un mondo." #: builtin/mainmenu/tab_online.lua #, fuzzy @@ -1316,9 +1315,8 @@ msgid "Login" msgstr "Accedi" #: builtin/mainmenu/tab_online.lua -#, fuzzy msgid "Number of mods: $1" -msgstr "Numero di thread emerge" +msgstr "Numero di moduli: $1" #: builtin/mainmenu/tab_online.lua #, fuzzy @@ -1330,11 +1328,12 @@ msgid "Ping" msgstr "Ping" #: builtin/mainmenu/tab_online.lua -#, fuzzy msgid "" "Players:\n" "$1" -msgstr "Client" +msgstr "" +"Giocatori:\n" +"$1" #: builtin/mainmenu/tab_online.lua msgid "" @@ -1343,6 +1342,10 @@ msgid "" "mod:\n" "player:" msgstr "" +"Possibili filtri\n" +"game:\n" +"mod:\n" +"player:" #: builtin/mainmenu/tab_online.lua msgid "Public Servers" @@ -1475,9 +1478,9 @@ msgid "Camera update enabled" msgstr "Aggiornamento telecamera abilitato" #: src/client/game.cpp -#, fuzzy msgid "Can't show block bounds (disabled by game or mod)" -msgstr "Impossibile mostrare i limiti del blocco (disabilitato da mod o gioco)" +msgstr "" +"Impossibile mostrare i limiti del blocco (disabilitato da modulo o gioco)" #: src/client/game.cpp msgid "Cinematic mode disabled" @@ -1489,11 +1492,11 @@ msgstr "Modalità cinematica attiva" #: src/client/game.cpp msgid "Client disconnected" -msgstr "Client disconnesso" +msgstr "Cliente disconnesso" #: src/client/game.cpp msgid "Client side scripting is disabled" -msgstr "Scripting su lato client disabilitato" +msgstr "Gli script lato cliente sono disabilitati" #: src/client/game.cpp msgid "Connecting to server..." @@ -1514,7 +1517,7 @@ msgstr "Impossibile risolvere l'indirizzo: %s" #: src/client/game.cpp msgid "Creating client..." -msgstr "Creazione del client..." +msgstr "Creazione del cliente..." #: src/client/game.cpp msgid "Creating server..." @@ -1527,7 +1530,7 @@ msgstr "Info debug mostrate" #: src/client/game.cpp #, c-format msgid "Error creating client: %s" -msgstr "Errore di creazione del client: %s" +msgstr "Errore di creazione del cliente: %s" #: src/client/game.cpp msgid "Fast mode disabled" @@ -1562,9 +1565,8 @@ msgid "Fog enabled" msgstr "Nebbia attivata" #: src/client/game.cpp -#, fuzzy msgid "Fog enabled by game or mod" -msgstr "Ingrandimento attualmente disabilitato dal gioco o da un mod" +msgstr "Nebbia abilitata dal gioco o da un modulo" #: src/client/game.cpp msgid "Item definitions..." @@ -1580,7 +1582,7 @@ msgstr "MiB/s" #: src/client/game.cpp msgid "Minimap currently disabled by game or mod" -msgstr "Minimappa attualmente disabilitata dal gioco o da una mod" +msgstr "Minimappa attualmente disabilitata dal gioco o da un modulo" #: src/client/game.cpp msgid "Multiplayer" @@ -1604,15 +1606,15 @@ msgstr "Definizione dei nodi..." #: src/client/game.cpp msgid "Pitch move mode disabled" -msgstr "Modalità inclinazione movimento disabilitata" +msgstr "Beccheggio disabilitato" #: src/client/game.cpp msgid "Pitch move mode enabled" -msgstr "Modalità inclinazione movimento abilitata" +msgstr "Beccheggio abilitato" #: src/client/game.cpp msgid "Profiler graph shown" -msgstr "Grafico profiler visualizzato" +msgstr "Profilatore visualizzato" #: src/client/game.cpp msgid "Resolving address..." @@ -1665,7 +1667,8 @@ msgstr "Raggio visivo illimitato abilitato" #: src/client/game.cpp msgid "Unlimited viewing range enabled, but forbidden by game or mod" -msgstr "Raggio visivo illimitato abilitato, ma vietato dal gioco o dal mod" +msgstr "" +"Raggio visivo illimitato abilitato, ma è bloccato dal gioco o da un modulo" #: src/client/game.cpp #, fuzzy, c-format @@ -1676,8 +1679,8 @@ msgstr "Il raggio visivo è al minimo: %d" #, c-format msgid "Viewing changed to %d (the minimum), but limited to %d by game or mod" msgstr "" -"Raggio visivo modificato a %d (il minimo), ma limitato a %d dal gioco o dal " -"mod" +"Raggio visivo modificato a %d (il minimo), ma limitato a %d dal gioco o da " +"un modulo" #: src/client/game.cpp #, c-format @@ -1694,13 +1697,13 @@ msgstr "Raggio visivo cambiato a %d" msgid "" "Viewing range changed to %d (the maximum), but limited to %d by game or mod" msgstr "" -"Raggio visivo modificato a %d (il massimo), ma limitato a %d dal gioco o dal " -"mod" +"Raggio visivo modificato a %d (il massimo), ma limitato a %d dal gioco o da " +"un modulo" #: src/client/game.cpp -#, fuzzy, c-format +#, c-format msgid "Viewing range changed to %d, but limited to %d by game or mod" -msgstr "Raggio visivo cambiato a %d" +msgstr "Raggio visivo cambiato a %d, ma limitato a %d dal gioco o da un modulo" #: src/client/game.cpp #, c-format @@ -1717,7 +1720,7 @@ msgstr "Struttura a fili visualizzata" #: src/client/game.cpp msgid "Zoom currently disabled by game or mod" -msgstr "Ingrandimento attualmente disabilitato dal gioco o da un mod" +msgstr "Zoom attualmente disabilitato dal gioco o da un modulo" #: src/client/game_formspec.cpp msgid "- Mode: " @@ -1819,7 +1822,7 @@ msgstr "Sei morto" #: src/client/gameui.cpp msgid "Chat currently disabled by game or mod" -msgstr "Chat attualmente disabilitata dal gioco o da un mod" +msgstr "Chat attualmente disabilitata dal gioco o da un modulo" #: src/client/gameui.cpp msgid "Chat hidden" @@ -2170,7 +2173,7 @@ msgstr "%s mancante:" msgid "" "Install and enable the required mods, or disable the mods causing errors." msgstr "" -"Installa e abilita le mod richieste o disabilita le mod che causano errori." +"Installa e abilita i moduli richiesti o disabilita quelli che causano errori." #: src/content/mod_configuration.cpp msgid "" @@ -2178,11 +2181,11 @@ msgid "" "the mods." msgstr "" "Nota: questo potrebbe essere causato da un ciclo di dipendenza, nel qual " -"caso prova ad aggiornare le mod." +"caso prova ad aggiornare i moduli." #: src/content/mod_configuration.cpp msgid "Some mods have unsatisfied dependencies:" -msgstr "Alcune mod hanno dipendenze non soddisfatte:" +msgstr "Alcuni moduli hanno dipendenze non soddisfatte:" #: src/gui/guiButtonKey.h #, fuzzy @@ -2203,11 +2206,11 @@ msgstr "Prosegui" #: src/gui/guiOpenURL.cpp msgid "Open" -msgstr "" +msgstr "Apri" #: src/gui/guiOpenURL.cpp msgid "Open URL?" -msgstr "" +msgstr "Apri URL?" #: src/gui/guiOpenURL.cpp #, fuzzy @@ -2250,13 +2253,12 @@ msgid "Done" msgstr "Fatto!" #: src/gui/touchscreeneditor.cpp -#, fuzzy msgid "Remove" -msgstr "Server remoto" +msgstr "Rimuovi" #: src/gui/touchscreeneditor.cpp msgid "Reset" -msgstr "" +msgstr "Ripristina" #: src/gui/touchscreeneditor.cpp msgid "Start dragging a button to add. Tap outside to cancel." @@ -2264,11 +2266,11 @@ msgstr "" #: src/gui/touchscreeneditor.cpp msgid "Tap a button to select it. Drag a button to move it." -msgstr "" +msgstr "Tocca un pulsante per selezionarlo. Trascinalo per spostarlo." #: src/gui/touchscreeneditor.cpp msgid "Tap outside to deselect." -msgstr "" +msgstr "Tocca fuori per deselezionare." #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Aux1" @@ -2280,7 +2282,7 @@ msgstr "Cambia vista" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Dig/punch/use" -msgstr "" +msgstr "Scava/colpisci/usa" #: src/gui/touchscreenlayout.cpp msgid "Drop" @@ -2301,16 +2303,15 @@ msgstr "ID del joystick" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Jump" -msgstr "Salta" +msgstr "Salto" #: src/gui/touchscreenlayout.cpp msgid "Overflow menu" msgstr "" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp -#, fuzzy msgid "Place/use" -msgstr "Tasto piazza" +msgstr "Piazza/usa" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Range select" @@ -2325,13 +2326,12 @@ msgid "Toggle chat log" msgstr "Log chat sì/no" #: src/gui/touchscreenlayout.cpp -#, fuzzy msgid "Toggle debug" -msgstr "Nebbia sì/no" +msgstr "Debug sì/no" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Toggle fast" -msgstr "Corsa sì/no" +msgstr "Veloce sì/no" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Toggle fly" @@ -2354,14 +2354,16 @@ msgid "" "Another client is connected with this name. If your client closed " "unexpectedly, try again in a minute." msgstr "" +"C'è già un altro cliente connesso con questo nome. Se è stato il tuo a " +"chiudersi di colpo, riprova tra un minuto." #: src/network/clientpackethandler.cpp msgid "Empty passwords are disallowed. Set a password and try again." -msgstr "" +msgstr "Non sono permesse password vuote. Imposta una password e riprova." #: src/network/clientpackethandler.cpp msgid "Internal server error" -msgstr "" +msgstr "Errore interno del server" #: src/network/clientpackethandler.cpp #, fuzzy @@ -2389,7 +2391,7 @@ msgstr "Nome già in uso. Scegli un altro nome, per favore" #: src/network/clientpackethandler.cpp msgid "Player name contains disallowed characters" -msgstr "" +msgstr "Il nome contiene caratteri non permessi" #: src/network/clientpackethandler.cpp #, fuzzy @@ -2404,31 +2406,35 @@ msgstr "Uscita dal gioco..." #: src/network/clientpackethandler.cpp msgid "" "The server has experienced an internal error. You will now be disconnected." -msgstr "" +msgstr "Il server ha avuto un errore interno. Sei statə scollegatə." #: src/network/clientpackethandler.cpp msgid "The server is running in singleplayer mode. You cannot connect." -msgstr "" +msgstr "Il server è in modalità giocatore singolo. Non puoi connetterti." #: src/network/clientpackethandler.cpp msgid "Too many users" -msgstr "" +msgstr "Troppɜ utenti" #: src/network/clientpackethandler.cpp msgid "Unknown disconnect reason." -msgstr "" +msgstr "Motivo della disconnessione: sconosciuto." #: src/network/clientpackethandler.cpp msgid "" "Your client sent something the server didn't expect. Try reconnecting or " "updating your client." msgstr "" +"Il server ha ricevuto qualcosa di inaspettato dal tuo cliente. Prova a " +"riconnetterti o ad aggiornare Luanti." #: src/network/clientpackethandler.cpp msgid "" "Your client's version is not supported.\n" "Please contact the server administrator." msgstr "" +"La versione del tuo cliente non è supportata.\n" +"Contatta un amministratore del server." #: src/server.cpp #, fuzzy, c-format @@ -2601,12 +2607,12 @@ msgstr "" #: src/settings_translation_file.cpp msgid "A message to be displayed to all clients when the server crashes." -msgstr "" -"Un messaggio da mostrare a tutti i client quando il server va in crash." +msgstr "Un messaggio da mostrare a tutti i clienti quando il server crasha." #: src/settings_translation_file.cpp msgid "A message to be displayed to all clients when the server shuts down." -msgstr "Un messaggio da mostrare a tutti i client quando il server chiude." +msgstr "" +"Un messaggio da mostrare a tutti i clienti quando il server viene spento." #: src/settings_translation_file.cpp msgid "ABM interval" @@ -2688,7 +2694,7 @@ msgstr "Usare l'aspetto 3D per le nuvole invece di quello piatto." #: src/settings_translation_file.cpp msgid "Allows liquids to be translucent." -msgstr "" +msgstr "Attiva la traslucenza sui liquidi." #: src/settings_translation_file.cpp msgid "" @@ -2741,11 +2747,11 @@ msgstr "Anti-Scalettatura:" #: src/settings_translation_file.cpp msgid "Anticheat flags" -msgstr "" +msgstr "Segnalini dell'anticheat" #: src/settings_translation_file.cpp msgid "Anticheat movement tolerance" -msgstr "" +msgstr "Tolleranza di movimento dell'anticheat" #: src/settings_translation_file.cpp msgid "Append item name" @@ -2791,7 +2797,6 @@ msgid "Ask to reconnect after crash" msgstr "Chiedi di riconnettersi dopo un crash" #: src/settings_translation_file.cpp -#, fuzzy msgid "" "At this distance the server will aggressively optimize which blocks are sent " "to\n" @@ -2804,17 +2809,14 @@ msgid "" "Stated in MapBlocks (16 nodes)." msgstr "" "A questa distanza il server ottimizzerà aggressivamente quali blocchi sono\n" -"inviati ai client.\n" +"inviati ai clienti.\n" "Potenzialmente valori piccoli migliorano molto le prestazioni, al costo di\n" -"difetti di disegno visibili (alcuni blocchi non saranno disegnati sott'acqua " -"e\n" -"nelle grotte, come a volte sul terreno).\n" +"difetti di renderizzazione (con alcuni blocchi nelle grotte).\n" "Impostarla a un valore maggiore di max_block_send_distance disabilita\n" "questa ottimizzazione.\n" "Fissata in blocchi mappa (16 nodi)." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "At this distance the server will perform a simpler and cheaper occlusion " "check.\n" @@ -2824,14 +2826,10 @@ msgid "" "This is especially useful for very large viewing range (upwards of 500).\n" "Stated in MapBlocks (16 nodes)." msgstr "" -"A questa distanza il server ottimizzerà aggressivamente quali blocchi sono\n" -"inviati ai client.\n" +"A questa distanza il server ottimizzerà aggressivamente l'occlusione.\n" "Potenzialmente valori piccoli migliorano molto le prestazioni, al costo di\n" -"difetti di disegno visibili (alcuni blocchi non saranno disegnati sott'acqua " -"e\n" -"nelle grotte, come a volte sul terreno).\n" -"Impostarla a un valore maggiore di max_block_send_distance disabilita\n" -"questa ottimizzazione.\n" +"difetti di renderizzazione (blocchi mancanti).\n" +"Torna utile soprattutto con un raggio di visione molto alto (sopra i 500).\n" "Fissata in blocchi mappa (16 nodi)." #: src/settings_translation_file.cpp @@ -2844,7 +2842,7 @@ msgstr "Salto automatico" #: src/settings_translation_file.cpp msgid "Automatically jump up single-node obstacles." -msgstr "Salta automaticamente su ostacoli di un nodo singolo." +msgstr "Salta automaticamente su ostacoli alti un nodo." #: src/settings_translation_file.cpp msgid "Automatically report to the serverlist." @@ -2852,7 +2850,7 @@ msgstr "Fa rapporto automatico all'elenco dei server." #: src/settings_translation_file.cpp msgid "Autoscaling mode" -msgstr "Modalità scalamento automatico" +msgstr "Scalatura automatica" #: src/settings_translation_file.cpp msgid "Aux1 key for climbing/descending" @@ -2953,7 +2951,7 @@ msgstr "Fluidità della telecamera" #: src/settings_translation_file.cpp msgid "Camera smoothing in cinematic mode" -msgstr "Fluidità della telecamera in modalità cinematic" +msgstr "Fluidità della telecamera in modalità cinematica" #: src/settings_translation_file.cpp msgid "Cave noise" @@ -3059,7 +3057,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Client" -msgstr "Client" +msgstr "Cliente" #: src/settings_translation_file.cpp #, fuzzy @@ -3068,29 +3066,27 @@ msgstr "Debugging" #: src/settings_translation_file.cpp msgid "Client Mesh Chunksize" -msgstr "Grandezza Client della Mesh del Chunk" +msgstr "Grandezza dei pezzi di mappa del cliente" #: src/settings_translation_file.cpp msgid "Client and Server" -msgstr "Client e server" +msgstr "Cliente e server" #: src/settings_translation_file.cpp msgid "Client modding" -msgstr "Modifica del client" +msgstr "Moddaggio del cliente" #: src/settings_translation_file.cpp msgid "Client side modding restrictions" -msgstr "Restrizioni delle modifiche del client" +msgstr "Restrizioni del moddaggio del cliente" #: src/settings_translation_file.cpp -#, fuzzy msgid "Client-side Modding" -msgstr "Mod lato client" +msgstr "Mod lato cliente" #: src/settings_translation_file.cpp -#, fuzzy msgid "Client-side node lookup range restriction" -msgstr "Restrizione dell'area di ricerca dei nodi su lato client" +msgstr "Restrizione dell'area di ricerca dei nodi lato cliente" #: src/settings_translation_file.cpp msgid "Climbing speed" @@ -3153,17 +3149,19 @@ msgid "" "Comma-separated list of mods that are allowed to access HTTP APIs, which\n" "allow them to upload and download data to/from the internet." msgstr "" -"Elenco separato da virgole di mod a cui è permesso l'accesso alle API HTTP,\n" -"che gli permettono di caricare e scaricare dati su/da internet." +"Elenco, separato da virgole, di moduli a cui è permesso l'accesso alle API " +"HTTP,\n" +"le quali permettono a tali moduli di caricare e scaricare dati su/da " +"internet." #: src/settings_translation_file.cpp msgid "" "Comma-separated list of trusted mods that are allowed to access insecure\n" "functions even when mod security is on (via request_insecure_environment())." msgstr "" -"Elenco separato da virgole delle mod fidate ai quali è permesso l'accesso a " +"Elenco separato da virgole dei moduli fidati ai quali è permesso l'accesso a " "funzioni non sicure\n" -"anche quando la sicurezza mod è attiva (tramite " +"anche quando la sicurezza moduli è attiva (tramite " "request_insecure_environment())." #: src/settings_translation_file.cpp @@ -3191,7 +3189,7 @@ msgid "" "9 - best compression, slowest" msgstr "" "Livello di compressione da utilizzare per l'invio dei blocchi-mappa al " -"client.\n" +"cliente.\n" "-1 - utilizza il livello di compressione predefinito\n" "0 - compressione minima, più veloce\n" "9 - compressione migliore, più lento" @@ -3323,9 +3321,8 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Decrease view range" -msgstr "Diminuisci raggio" +msgstr "Diminuisci raggio visivo" #: src/settings_translation_file.cpp #, fuzzy @@ -3376,7 +3373,6 @@ msgstr "" "ma utilizza più risorse." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "Define the oldest clients allowed to connect.\n" "Older clients are compatible in the sense that they will not crash when " @@ -3388,11 +3384,11 @@ msgid "" "Luanti still enforces its own internal minimum, and enabling\n" "strict_protocol_version_checking will effectively override this." msgstr "" -"Abilitare per impedire ai client obsoleti di connettersi.\n" -"I client più vecchi sono compatibili nel senso che non andranno in crash " -"alla connessione\n" +"Abilitare per impedire ai clienti obsoleti di connettersi.\n" +"I clienti più vecchi sono compatibili nel senso che non crasheranno al " +"connettersi\n" "ai nuovi server, ma potrebbero non supportare tutte le nuove caratteristiche " -"che ti aspetti." +"che ci si aspetta." #: src/settings_translation_file.cpp msgid "Defines areas where trees have apples." @@ -3466,9 +3462,9 @@ msgid "" "Delay between mesh updates on the client in ms. Increasing this will slow\n" "down the rate of mesh updates, thus reducing jitter on slower clients." msgstr "" -"Ritardo in ms tra gli aggiornamenti delle mesh sul client. Aumentandolo si\n" +"Ritardo in ms degli aggiornamenti delle mesh lato cliente. Aumentandolo si\n" "ritarderà il ritmo di aggiornamento delle mesh, riducendo così lo sfarfallio " -"sui client più lenti." +"sui clienti più lenti." #: src/settings_translation_file.cpp msgid "Delay in sending blocks after building" @@ -3539,12 +3535,11 @@ msgstr "Nome di dominio del server, da mostrarsi nell'elenco dei server." #: src/settings_translation_file.cpp msgid "Double tap jump for fly" -msgstr "Doppio \"salta\" per volare" +msgstr "Premi due volte salto per volare" #: src/settings_translation_file.cpp msgid "Double-tapping the jump key toggles fly mode." -msgstr "" -"Premendo due volte il tasto di salto si (dis)attiva la modalità di volo." +msgstr "Premendo due volte il tasto salto si (dis)attiva la modalità volo." #: src/settings_translation_file.cpp msgid "" @@ -3555,9 +3550,8 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Drop item" -msgstr "Tasto butta oggetto" +msgstr "Getta oggetto" #: src/settings_translation_file.cpp msgid "Dump the mapgen debug information." @@ -3602,7 +3596,7 @@ msgid "" "Enable IPv6 support (for both client and server).\n" "Required for IPv6 connections to work at all." msgstr "" -"Abilitare il supporto IPv6 (sia per il client che per il server).\n" +"Abilitare il supporto IPv6 (sia per il cliente che per il server).\n" "Necessario per il funzionamento delle connessioni IPv6." #: src/settings_translation_file.cpp @@ -3611,13 +3605,17 @@ msgid "" "Note that clients will be able to connect with both IPv4 and IPv6.\n" "Ignored if bind_address is set." msgstr "" +"Abilita il supporto per IPv6 sul server.\n" +"Notare che i clienti saranno in grado di connettersi sia tramite IPv4 che " +"IPv6.\n" +"Ignorato se bind_address è definito." #: src/settings_translation_file.cpp msgid "" "Enable Lua modding support on client.\n" "This support is experimental and API can change." msgstr "" -"Abilita il supporto per le modifiche tramite Lua sul client.\n" +"Abilita il supporto per il moddaggio tramite Lua sul cliente.\n" "Questo supporto è sperimentale e l'API potrebbe cambiare." #: src/settings_translation_file.cpp @@ -3674,24 +3672,23 @@ msgstr "Abilita i joystick. Richiede un riavvio del gioco per avere effetto" #: src/settings_translation_file.cpp msgid "Enable mod channels support." -msgstr "Abilita il supporto canali mod." +msgstr "Abilita il supporto canali per i moduli." #: src/settings_translation_file.cpp msgid "Enable mod security" -msgstr "Abilita la sicurezza mod" +msgstr "Abilita la sicurezza moduli" #: src/settings_translation_file.cpp msgid "Enable mouse wheel (scroll) for item selection in hotbar." msgstr "" "Abilita lo scorrimento della rotellina del mouse per la selezione degli " -"oggetti nella hotbar." +"oggetti nella barra delle azioni." #: src/settings_translation_file.cpp -#, fuzzy msgid "Enable random mod loading (mainly used for testing)." msgstr "" -"Abilita l'ingresso di dati casuali da parte dell'utente (utilizzato solo per " -"i test)." +"Abilita il caricamento casuale di moduli (usando principalmente per fare dei " +"test)." #: src/settings_translation_file.cpp msgid "Enable random user input (only used for testing)." @@ -3718,11 +3715,11 @@ msgid "" "to new servers, but they may not support all new features that you are " "expecting." msgstr "" -"Abilitare per impedire ai client obsoleti di connettersi.\n" -"I client più vecchi sono compatibili nel senso che non andranno in crash " -"alla connessione\n" +"Abilitare per impedire ai clienti obsoleti di connettersi.\n" +"I clienti più vecchi sono compatibili nel senso che non crasheranno al " +"connettersi\n" "ai nuovi server, ma potrebbero non supportare tutte le nuove caratteristiche " -"che ti aspetti." +"che ci si aspetta." #: src/settings_translation_file.cpp msgid "Enable updates available indicator on content tab" @@ -3852,11 +3849,11 @@ msgstr "Percorso del carattere di ripiego" #: src/settings_translation_file.cpp msgid "Fast mode acceleration" -msgstr "Accelerazione della modalità veloce" +msgstr "Accelerazione in modalità veloce" #: src/settings_translation_file.cpp msgid "Fast mode speed" -msgstr "Velocità della modalità veloce" +msgstr "Velocità in modalità veloce" #: src/settings_translation_file.cpp msgid "Field of view" @@ -3872,7 +3869,7 @@ msgid "" "the\n" "Multiplayer Tab." msgstr "" -"File in client/serverlist/ contenente i vostri server preferiti mostrati " +"File in client/serverlist/ contenente i tuoi server preferiti mostrati " "nella\n" "scheda di gioco in rete." @@ -4018,9 +4015,9 @@ msgstr "" "divisibili per questo\n" "valore, in pixel, in caso di caratteri in stile pixel che non vengono " "ridimensionati correttamente.\n" -"Per esempio, un carattere pixelato alto 16 pixels richiederebbe che questo " +"Per esempio, un carattere pixelato alto 16 pixel richiederebbe che questo " "sia 16, così sarà sempre\n" -"solo grande 16, 32, 48, etc., e una mod che richiede una grandezza di 25 " +"solo grande 16, 32, 48, etc., e un modulo che richiede una grandezza di 25 " "otterrà 32." #: src/settings_translation_file.cpp @@ -4074,15 +4071,15 @@ msgid "" "From how far blocks are generated for clients, stated in mapblocks (16 " "nodes)." msgstr "" -"Da che distanza vengono generati i blocchi per i client, fissata in blocchi " +"Da che distanza vengono generati i blocchi per i clienti, fissata in blocchi " "mappa (16 nodi)." #: src/settings_translation_file.cpp msgid "" "From how far blocks are sent to clients, stated in mapblocks (16 nodes)." msgstr "" -"Da che distanza i blocchi sono inviati ai client, fissata in blocchi mappa " -"(16 nodi)." +"Da che distanza i blocchi sono inviati ai clienti, fissata in blocchi mappa (" +"16 nodi)." #: src/settings_translation_file.cpp msgid "" @@ -4092,7 +4089,7 @@ msgid "" "to maintain active objects up to this distance in the direction the\n" "player is looking. (This can avoid mobs suddenly disappearing from view)" msgstr "" -"Da che distanza il client sa degli oggetti, fissata in blocchi mappa (16 " +"Da che distanza il cliente rileva gli oggetti, fissata in blocchi mappa (16 " "nodi).\n" "\n" "Impostarla maggiore di active_block_range provocherà il mantenimento\n" @@ -4106,7 +4103,7 @@ msgstr "Schermo intero" #: src/settings_translation_file.cpp msgid "Fullscreen mode." -msgstr "Modalità a schermo intero." +msgstr "Schermo intero." #: src/settings_translation_file.cpp #, fuzzy @@ -4179,7 +4176,7 @@ msgstr "Rumore del terreno" #: src/settings_translation_file.cpp msgid "HTTP mods" -msgstr "Mod HTTP" +msgstr "Moduli HTTP" #: src/settings_translation_file.cpp msgid "HUD" @@ -4198,10 +4195,10 @@ msgid "" msgstr "" "Gestione delle chiamate API Lua deprecate:\n" "- none (nessuno): non registra le chiamate obsolete\n" -"- log (registro): imita e registra il backtrace di una chiamata obsoleta " -"(impostazione predefinita).\n" -"- error (errore): interrompe l'utilizzo della chiamata deprecata " -"(consigliata per gli sviluppatori di mod)." +"- log (registro): imita e registra il backtrace di una chiamata obsoleta (" +"impostazione predefinita).\n" +"- error (errore): interrompe l'utilizzo della chiamata deprecata (" +"consigliata per chi sviluppa moduli)." #: src/settings_translation_file.cpp msgid "" @@ -4211,7 +4208,7 @@ msgid "" "call).\n" "* Instrument the sampler being used to update the statistics." msgstr "" -"Fare in modo che il generatore di profili si predisponga da sé:\n" +"Fare in modo che il profilatore si predisponga da sé:\n" "* Predisporre una funzione vuota.\n" "Ciò stima il sovraccarico che la predisposizione aggiunge (+1 chiamata di " "funzione).\n" @@ -4226,11 +4223,8 @@ msgid "Heat noise" msgstr "Rumore del calore" #: src/settings_translation_file.cpp -#, fuzzy msgid "Height component of the initial window size." -msgstr "" -"Componente altezza della dimensione iniziale della finestra. Ignorata in " -"modalità a schermo intero." +msgstr "Componente altezza della dimensione iniziale della finestra." #: src/settings_translation_file.cpp msgid "Height noise" @@ -4293,196 +4287,160 @@ msgstr "" "in nodi al secondo per secondo." #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 1" -msgstr "Tasto riquadro 1 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 1" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 10" -msgstr "Tasto riquadro 10 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 10" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 11" -msgstr "Tasto riquadro 11 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 11" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 12" -msgstr "Tasto riquadro 12 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 12" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 13" -msgstr "Tasto riquadro 13 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 13" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 14" -msgstr "Tasto riquadro 14 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 14" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 15" -msgstr "Tasto riquadro 15 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 15" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 16" -msgstr "Tasto riquadro 16 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 16" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 17" -msgstr "Tasto riquadro 17 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 17" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 18" -msgstr "Tasto riquadro 18 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 18" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 19" -msgstr "Tasto riquadro 19 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 19" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 2" -msgstr "Tasto riquadro 2 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 2" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 20" -msgstr "Tasto riquadro 20 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 20" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 21" -msgstr "Tasto riquadro 21 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 21" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 22" -msgstr "Tasto riquadro 22 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 22" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 23" -msgstr "Tasto riquadro 23 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 23" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 24" -msgstr "Tasto riquadro 24 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 24" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 25" -msgstr "Tasto riquadro 25 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 25" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 26" -msgstr "Tasto riquadro 26 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 26" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 27" -msgstr "Tasto riquadro 27 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 27" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 28" -msgstr "Tasto riquadro 28 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 28" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 29" -msgstr "Tasto riquadro 29 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 29" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 3" -msgstr "Tasto riquadro 3 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 3" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 30" -msgstr "Tasto riquadro 30 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 30" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 31" -msgstr "Tasto riquadro 31 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 31" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 32" -msgstr "Tasto riquadro 32 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 32" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 4" -msgstr "Tasto riquadro 4 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 4" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 5" -msgstr "Tasto riquadro 5 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 5" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 6" -msgstr "Tasto riquadro 6 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 6" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 7" -msgstr "Tasto riquadro 7 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 7" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 8" -msgstr "Tasto riquadro 8 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 8" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 9" -msgstr "Tasto riquadro 9 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 9" #: src/settings_translation_file.cpp msgid "Hotbar: Enable mouse wheel for selection" -msgstr "Hotbar: attiva la rotellina del mouse per la selezione" +msgstr "Barra delle azioni: attiva la rotellina del mouse per la selezione" #: src/settings_translation_file.cpp msgid "Hotbar: Invert mouse wheel direction" -msgstr "Hotbar: inverti la direzione della rotellina del mouse" +msgstr "Barra delle azioni: inverti la direzione della rotellina del mouse" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar: select next item" -msgstr "Tasto successivo della barra di scelta rapida" +msgstr "Barra delle azioni: seleziona prossimo oggetto" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar: select previous item" -msgstr "Tasto precedente della barra di scelta rapida" +msgstr "Barra delle azioni: seleziona oggetto precedente" #: src/settings_translation_file.cpp msgid "How deep to make rivers." msgstr "Quanto fare profondi i fiumi." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "How fast liquid waves will move. Higher = faster.\n" "If negative, liquid waves will move backwards." msgstr "" "A quale velocità si muovono le onde dei liquidi. Maggiore = più veloce.\n" -"Se negativa, le onde dei liquidi si sposteranno all'indietro.\n" -"Richiede l'abilitazione dei liquidi ondeggianti." +"Se negativa, le onde dei liquidi si sposteranno all'indietro." #: src/settings_translation_file.cpp msgid "" @@ -4500,7 +4458,7 @@ msgid "" "How much you are slowed down when moving inside a liquid.\n" "Decrease this to increase liquid resistance to movement." msgstr "" -"Quanto sei rallentato mentre ti muovi dentro un liquido.\n" +"Quanto sei rallentatə mentre ti muovi dentro un liquido.\n" "Riducila per aumentare la resistenza del liquido al movimento." #: src/settings_translation_file.cpp @@ -4550,6 +4508,10 @@ msgid "" "ContentDB to\n" "check for package updates when opening the mainmenu." msgstr "" +"Se abilitato e si hanno pacchetti provenienti da ContentDB installati, " +"Luanti potrebbe contattare\n" +"ContentDB per controllare eventuali aggiornamenti quando viene aperto il " +"menù principale." #: src/settings_translation_file.cpp msgid "" @@ -4600,16 +4562,17 @@ msgstr "" #: src/settings_translation_file.cpp msgid "If enabled, the \"Aux1\" key will toggle when pressed." -msgstr "" +msgstr "Se abilitato, il tasto \"Aux1\" si azionerà quando premuto." #: src/settings_translation_file.cpp msgid "" "If enabled, the \"Sneak\" key will toggle when pressed.\n" "This functionality is ignored when fly is enabled." msgstr "" +"Se abilitato, il tasto \"Furtivo\" si azionerà quando premuto.\n" +"Questa funzionalità è ignorata quando la modalità volo è abilitata." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "If enabled, the server will perform map block occlusion culling based on\n" "on the eye position of the player. This can reduce the number of blocks\n" @@ -4620,7 +4583,7 @@ msgstr "" "basandosi\n" "sulla posizione degli occhi del giocatore. Questo può ridurre del 50-80% il " "numero dei blocchi\n" -"inviati al client. Il client non riceverà più la maggior parte degli " +"inviati al cliente. Il cliente non riceverà più la maggior parte degli " "invisibili\n" "cosicché l'utilità della modalità incorporea è ridotta." @@ -4661,7 +4624,7 @@ msgid "" "deleting an older debug.txt.1 if it exists.\n" "debug.txt is only moved if this setting is positive." msgstr "" -"Se la dimensione del file debug.txt supera il numero di Mb indicati in\n" +"Se la dimensione del file debug.txt supera il numero di MB indicati in\n" "questa impostazione quando viene aperto, il file viene rinominato in " "debug.txt.1,\n" "eliminando un eventuale debug.txt.1 più vecchio.\n" @@ -4690,9 +4653,8 @@ msgid "In-game chat console height, between 0.1 (10%) and 1.0 (100%)." msgstr "Altezza della console di chat nel gioco, tra 0.1 (10%) e 1.0 (100%)." #: src/settings_translation_file.cpp -#, fuzzy msgid "Increase view range" -msgstr "Aumenta raggio" +msgstr "Aumenta raggio visivo" #: src/settings_translation_file.cpp #, fuzzy @@ -4759,13 +4721,13 @@ msgstr "Animazioni degli oggetti dell'inventario" #: src/settings_translation_file.cpp msgid "Invert mouse" -msgstr "Invertire il mouse" +msgstr "Mouse invertito" #: src/settings_translation_file.cpp msgid "Invert mouse wheel (scroll) direction for item selection in hotbar." msgstr "" -"Inverte la direzione di scorrimento della rotellina del mouse per la " -"selezione degli oggetti nella hotbar." +"Inverte lo scorrimento della rotellina del mouse per la selezione degli " +"oggetti nella barra delle azioni." #: src/settings_translation_file.cpp msgid "Invert vertical mouse movement." @@ -4892,15 +4854,15 @@ msgstr "Velocità di salto" #: src/settings_translation_file.cpp msgid "Key for decreasing the viewing range." -msgstr "" +msgstr "Tasto per diminuire il raggio visivo." #: src/settings_translation_file.cpp msgid "Key for decreasing the volume." -msgstr "" +msgstr "Tasto per abbassare il volume." #: src/settings_translation_file.cpp msgid "Key for decrementing the selected value in Quicktune." -msgstr "" +msgstr "Tasto per diminuire il valore di Quicktune." #: src/settings_translation_file.cpp msgid "" @@ -4910,27 +4872,27 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for dropping the currently selected item." -msgstr "" +msgstr "Tasto per gettare l'oggetto che si ha in mano." #: src/settings_translation_file.cpp msgid "Key for increasing the viewing range." -msgstr "" +msgstr "Tasto per aumentare il raggio visivo." #: src/settings_translation_file.cpp msgid "Key for increasing the volume." -msgstr "" +msgstr "Tasto per alzare il volume." #: src/settings_translation_file.cpp msgid "Key for incrementing the selected value in Quicktune." -msgstr "" +msgstr "Tasto per aumentare il valore di Quicktune." #: src/settings_translation_file.cpp msgid "Key for jumping." -msgstr "" +msgstr "Tasto per saltare." #: src/settings_translation_file.cpp msgid "Key for moving fast in fast mode." -msgstr "" +msgstr "Tasto per muoversi rapidamente in modalità veloce." #: src/settings_translation_file.cpp #, fuzzy @@ -4945,35 +4907,36 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for moving the player forward." -msgstr "" +msgstr "Tasto per muovere il giocatore in avanti." #: src/settings_translation_file.cpp msgid "Key for moving the player left." -msgstr "" +msgstr "Tasto per muovere il giocatore a sinistra." #: src/settings_translation_file.cpp msgid "Key for moving the player right." -msgstr "" +msgstr "Tasto per muovere il giocatore a destra." #: src/settings_translation_file.cpp msgid "Key for muting the game." -msgstr "" +msgstr "Tasto per mutare il gioco." #: src/settings_translation_file.cpp msgid "Key for opening the chat window to type commands." -msgstr "" +msgstr "Tasto per aprire la finestra della chat, pronta per scrivere comandi." #: src/settings_translation_file.cpp msgid "Key for opening the chat window to type local commands." msgstr "" +"Tasto per aprire la finestra della chat, pronta per scrivere comandi locali." #: src/settings_translation_file.cpp msgid "Key for opening the chat window." -msgstr "" +msgstr "Tasto per aprire la finestra della chat." #: src/settings_translation_file.cpp msgid "Key for opening the inventory." -msgstr "" +msgstr "Tasto per aprire l'inventario." #: src/settings_translation_file.cpp msgid "" @@ -4983,139 +4946,139 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for selecting the 11th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare l'11° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 12th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 12° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 13th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 13° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 14th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 14° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 15th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 15° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 16th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 16° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 17th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 17° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 18th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 18° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 19th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 19° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 20th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 20° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 21st hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 21° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 22nd hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 22° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 23rd hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 23° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 24th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 24° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 25th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 25° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 26th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 26° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 27th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 27° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 28th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 28° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 29th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 29° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 30th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 30° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 31st hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 31° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 32nd hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 32° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the eighth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare l'8° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the fifth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 5° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the first hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 1° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the fourth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 4° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the next item in the hotbar." -msgstr "" +msgstr "Tasto per selezionare il prossimo oggetto nella barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the ninth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 9° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the previous item in the hotbar." -msgstr "" +msgstr "Tasto per selezionare l'oggetto precedente nella barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the second hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 2° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the seventh hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 7° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the sixth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 6° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the tenth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 10° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the third hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 3° casella della barra delle azioni." #: src/settings_translation_file.cpp #, fuzzy @@ -5132,7 +5095,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for switching between first- and third-person camera." -msgstr "" +msgstr "Tasto per ruotare tra prima e terza persona." #: src/settings_translation_file.cpp msgid "Key for switching to the next entry in Quicktune." @@ -5149,37 +5112,35 @@ msgstr "Formato degli screenshot." #: src/settings_translation_file.cpp msgid "Key for toggling autoforward." -msgstr "" +msgstr "Tasto per (dis)attivare l'avanzamento automatico." #: src/settings_translation_file.cpp -#, fuzzy msgid "Key for toggling cinematic mode." -msgstr "Fluidità della telecamera in modalità cinematic" +msgstr "Tasto per attivare e disattivare la modalità cinematica." #: src/settings_translation_file.cpp msgid "Key for toggling display of minimap." -msgstr "" +msgstr "Tasto per (dis)attivare la minimappa." #: src/settings_translation_file.cpp msgid "Key for toggling fast mode." -msgstr "" +msgstr "Tasto per attivare e disattivare la modalità veloce." #: src/settings_translation_file.cpp msgid "Key for toggling flying." -msgstr "" +msgstr "Tasto per (dis)attivare il volo." #: src/settings_translation_file.cpp -#, fuzzy msgid "Key for toggling fullscreen mode." -msgstr "Modalità a schermo intero." +msgstr "Tasto per attivare e disattivare la modalità a schermo intero." #: src/settings_translation_file.cpp msgid "Key for toggling noclip mode." -msgstr "" +msgstr "Tasto per attivare e disattivare la modalità incorporea." #: src/settings_translation_file.cpp msgid "Key for toggling pitch move mode." -msgstr "" +msgstr "Tasto per attivare e disattivare il beccheggio." #: src/settings_translation_file.cpp msgid "Key for toggling the camera update. Only usable with 'debug' privilege." @@ -5212,20 +5173,20 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for toggling the display of the profiler. Used for development." msgstr "" +"Tasto per alternare la visualizzazione del profilatore. Usato per fini di " +"sviluppo." #: src/settings_translation_file.cpp -#, fuzzy msgid "Key for toggling unlimited view range." -msgstr "Raggio visivo illimitato disabilitato" +msgstr "Tasto per (dis)attivare il raggio visivo illimitato." #: src/settings_translation_file.cpp msgid "Key to use view zoom when possible." msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Keybindings" -msgstr "Mappatura dei tasti." +msgstr "Mappatura dei tasti" #: src/settings_translation_file.cpp msgid "Keyboard and Mouse" @@ -5265,9 +5226,8 @@ msgid "Large cave proportion flooded" msgstr "Proporzione inondata della grotta grande" #: src/settings_translation_file.cpp -#, fuzzy msgid "Large chat console" -msgstr "Tasto console grande di chat" +msgstr "Console" #: src/settings_translation_file.cpp msgid "Leaves style" @@ -5401,7 +5361,8 @@ msgstr "" "- Recupero dei file multimediali se il server usa l'impostazione " "remote_media.\n" "- Scaricamento dell'elenco dei server e annuncio del server.\n" -"- Scaricamenti effettuati dal menu principale (per es. il gestore mod.).\n" +"- Scaricamenti effettuati dal menu principale (per es. il gestore " +"moduli.).\n" "Ha effetto solo se compilato con cURL." #: src/settings_translation_file.cpp @@ -5439,7 +5400,7 @@ msgstr "Scatto di aggiornamento del liquido" #: src/settings_translation_file.cpp msgid "Load the game profiler" -msgstr "Caricare il generatore di profili del gioco" +msgstr "Caricare il profilatore del gioco" #: src/settings_translation_file.cpp msgid "" @@ -5447,10 +5408,9 @@ msgid "" "Provides a /profiler command to access the compiled profile.\n" "Useful for mod developers and server operators." msgstr "" -"Carica il generatore di profili per raccogliere i dati di profilazione del " -"gioco.\n" +"Carica il profilatore per raccogliere i dati di profilazione del gioco.\n" "Fornisce un comando /profiler per accedere al profilo compilato.\n" -"Utile per sviluppatori di moduli e operatori di server." +"Utile per chi sviluppa moduli e chi opera dei server." #: src/settings_translation_file.cpp msgid "Loading Block Modifiers" @@ -5699,7 +5659,7 @@ msgstr "Numero massimo di blocchi caricati a forza" #: src/settings_translation_file.cpp msgid "Maximum hotbar width" -msgstr "Larghezza massima della barra di scelta rapida" +msgstr "Larghezza massima della barra delle azioni" #: src/settings_translation_file.cpp msgid "Maximum limit of random number of large caves per mapchunk." @@ -5724,9 +5684,9 @@ msgid "" "The maximum total count is calculated dynamically:\n" "max_total = ceil((#clients + max_users) * per_client / 4)" msgstr "" -"Numero massimo di blocchi inviati simultaneamente per client.\n" +"Numero massimo di blocchi inviati simultaneamente per clientecliente.\n" "Il conto totale massimo è calcolato dinamicamente:\n" -"tot_max = arrotonda((N°client + max_utenti) * per_client / 4)" +"tot_max = arrotonda((N°clienti + max_utenti) * per_cliente / 4)" #: src/settings_translation_file.cpp msgid "Maximum number of blocks that can be queued for loading." @@ -5797,13 +5757,13 @@ msgid "" "Maximum proportion of current window to be used for hotbar.\n" "Useful if there's something to be displayed right or left of hotbar." msgstr "" -"Porzione massima della finestra attuale da usarsi per la barra di scelta " -"rapida.\n" +"Porzione massima della finestra attuale da usarsi per la barra delle azioni." +"\n" "Utile se c'è qualcosa da mostrare a destra o sinistra della barra." #: src/settings_translation_file.cpp msgid "Maximum simultaneous block sends per client" -msgstr "Invii simultanei massimi di blocchi per client" +msgstr "Invii simultanei massimi di blocchi per cliente" #: src/settings_translation_file.cpp #, fuzzy @@ -5825,8 +5785,8 @@ msgid "" "Maximum time a file download (e.g. a mod download) may take, stated in " "milliseconds." msgstr "" -"Tempo massimo in millisecondi che può richiedere lo scaricamento di un file " -"(es. un mod)." +"Tempo massimo in millisecondi che può richiedere lo scaricamento di un file (" +"es. un modulo)." #: src/settings_translation_file.cpp msgid "" @@ -5896,7 +5856,7 @@ msgstr "Sicurezza Mod" #: src/settings_translation_file.cpp msgid "Mod channels" -msgstr "Canali mod" +msgstr "Canali per i moduli" #: src/settings_translation_file.cpp msgid "Modifies the size of the HUD elements." @@ -5939,28 +5899,24 @@ msgid "Mouse sensitivity multiplier." msgstr "Moltiplicatore della sensibilità del mouse." #: src/settings_translation_file.cpp -#, fuzzy msgid "Move backward" -msgstr "Indietreggia" +msgstr "Indietro" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move forward" -msgstr "Avanzam. autom." +msgstr "Avanti" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move left" -msgstr "Movimento" +msgstr "Sinistra" #: src/settings_translation_file.cpp msgid "Move right" -msgstr "" +msgstr "Destra" #: src/settings_translation_file.cpp -#, fuzzy msgid "Movement threshold" -msgstr "Soglia della caverna" +msgstr "Soglia di movimento" #: src/settings_translation_file.cpp msgid "Mud noise" @@ -5987,14 +5943,13 @@ msgstr "" "- 'floatlands' in v7 (disabilitato per impostazione predefinita)." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "Name of the player.\n" "When running a server, a client connecting with this name is admin.\n" "When starting from the main menu, this is overridden." msgstr "" "Nome del giocatore.\n" -"Quando si esegue un server, i client che si connettono con questo nome sono " +"Quando si esegue un server, i clienti che si connettono con questo nome sono " "amministratori.\n" "Quando si avvia dal menu principale, questo viene scavalcato." @@ -6118,7 +6073,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Open chat" -msgstr "" +msgstr "Apri chat" #: src/settings_translation_file.cpp #, fuzzy @@ -6149,9 +6104,8 @@ msgid "Optional override for chat weblink color." msgstr "Sovrascrittura opzionale per i colori dei link web in chat." #: src/settings_translation_file.cpp -#, fuzzy msgid "Other Effects" -msgstr "Effetti grafici" +msgstr "Altri effetti" #: src/settings_translation_file.cpp msgid "" @@ -6247,7 +6201,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Prevent mods from doing insecure things like running shell commands." msgstr "" -"Impedisce che i mod facciano cose non sicure come eseguire comandi della " +"Impedisce che i moduli facciano cose non sicure come eseguire comandi della " "shell." #: src/settings_translation_file.cpp @@ -6265,7 +6219,7 @@ msgstr "Privilegi che i giocatori con basic_privs possono concedere" #: src/settings_translation_file.cpp msgid "Profiler" -msgstr "Generatore di profili" +msgstr "Profilatore" #: src/settings_translation_file.cpp msgid "Prometheus listener address" @@ -6339,7 +6293,7 @@ msgstr "Dati in ingresso casuali" #: src/settings_translation_file.cpp msgid "Random mod load order" -msgstr "" +msgstr "Ordine casuale di caricamento moduli" #: src/settings_translation_file.cpp msgid "Recent Chat Messages" @@ -6363,7 +6317,7 @@ msgid "" "Remove color codes from incoming chat messages\n" "Use this to stop players from being able to use color in their messages" msgstr "" -"Leva i codici di colore dai messaggi di chat in arrivo\n" +"Rimuove i codici di colore dai messaggi di chat in arrivo\n" "Usalo per impedire ai giocatori di usare i colori nei loro messaggi" #: src/settings_translation_file.cpp @@ -6387,18 +6341,17 @@ msgid "" "csm_restriction_noderange)\n" "READ_PLAYERINFO: 32 (disable get_player_names call client-side)" msgstr "" -"Restringe l'accesso di certe funzioni lato-client sui server.\n" +"Restringe l'accesso di certe funzioni lato cliente sui server.\n" "Combina i valori byte sottostanti per restringere le caratteristiche\n" -"lato-client, o imposta a 0 per nessuna restrizione:\n" -"LOAD_CLIENT_MODS: 1 (disabilita il caricamento di mod forniti dal client)\n" -"CHAT_MESSAGES: 2 (disabilita la chiamata di send_chat_message su lato-" -"client)\n" -"READ_ITEMDEFS: 4 (disabilita la chiamata di get_item_def su lato-client)\n" -"READ_NODEDEFS: 8 (disabilita la chiamata di get_node_def su lato-client)\n" -"LOOKUP_NODES_LIMIT: 16 (limita la chiamata get_node su lato-client a\n" +"lato cliente, o imposta a 0 per nessuna restrizione:\n" +"LOAD_CLIENT_MODS: 1 (disabilita il caricamento di moduli forniti dal cliente)" +"\n" +"CHAT_MESSAGES: 2 (disabilita la chiamata di send_chat_message lato cliente)\n" +"READ_ITEMDEFS: 4 (disabilita la chiamata di get_item_def lato cliente)\n" +"READ_NODEDEFS: 8 (disabilita la chiamata di get_node_def lato cliente)\n" +"LOOKUP_NODES_LIMIT: 16 (limita la chiamata get_node lato cliente a\n" "csm_restriction_noderange)\n" -"READ_PLAYERINFO: 32 (disabilita la chiamata di get_player_names su lato-" -"client)" +"READ_PLAYERINFO: 32 (disabilita la chiamata di get_player_names lato cliente)" #: src/settings_translation_file.cpp msgid "Ridge mountain spread noise" @@ -6463,7 +6416,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Save the map received by the client on disk." -msgstr "Salvare su disco la mappa ricevuta dal client." +msgstr "Salvare su disco la mappa ricevuta dal cliente." #: src/settings_translation_file.cpp msgid "" @@ -6765,7 +6718,7 @@ msgid "" "Set the maximum length of a chat message (in characters) sent by clients." msgstr "" "Imposta la lunghezza massima (in caratteri) di un messaggio di chat inviato " -"dai client." +"dai clienti." #: src/settings_translation_file.cpp msgid "" @@ -6919,7 +6872,7 @@ msgid "" "draw calls, benefiting especially high-end GPUs.\n" "Systems with a low-end GPU (or no GPU) would benefit from smaller values." msgstr "" -"Lunghezza del lato di un cubo di blocchi mappa che il client considererà " +"Lunghezza del lato di un cubo di blocchi mappa che il cliente considererà " "assieme\n" "durante la generazione delle mesh.\n" "Valori maggiori aumentano l'utilizzo della GPU riducendo il numero di\n" @@ -7040,7 +6993,7 @@ msgid "" "(obviously, remote_media should end with a slash).\n" "Files that are not present will be fetched the usual way." msgstr "" -"Specifica l'URL da cui il client recupera i file multimediali invece di " +"Specifica l'URL da cui il cliente recupera i file multimediali invece di " "usare UDP.\n" "$filename dovrebbe essere accessibile da $remote_media$filename tramite\n" "cURL (ovviamente, remote_media dovrebbe finire con una barra).\n" @@ -7053,7 +7006,7 @@ msgid "" "items." msgstr "" "Fissa la dimensione predefinita della pila di nodi, oggetti e strumenti.\n" -"Si noti che mod o giochi possono impostare esplicitamente una pila per " +"Si noti che moduli o giochi possono impostare esplicitamente una pila per " "alcuni (o tutti) gli oggetti." #: src/settings_translation_file.cpp @@ -7271,6 +7224,16 @@ msgid "" "Known from the classic Luanti mobile controls.\n" "Combat is more or less impossible." msgstr "" +"L'azione per prendere a pugni giocanti/entità.\n" +"Può essere sovrascritto da giochi e moduli.\n" +"\n" +"* Tocco rapido\n" +"Facile da usare e diffuso su altri giochi che non possono essere nominati.\n" +"\n" +"* Tocco prolungato\n" +"Conosciuto dalla comunità di Luanti in quanto è stato il controllo " +"predefinito da telefono.\n" +"Combattere è pressoché impossibile." #: src/settings_translation_file.cpp msgid "The identifier of the joystick to use" @@ -7294,13 +7257,11 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "" "The length in pixels after which a touch interaction is considered movement." -msgstr "La distanza in pixel richiesta per avviare l'interazione touch screen." +msgstr "La distanza in pixel dopo la quale un tocco è considerato movimento." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "The maximum height of the surface of waving liquids.\n" "4.0 = Wave height is two nodes.\n" @@ -7310,8 +7271,7 @@ msgstr "" "L'altezza massima della superficie dei liquidi ondulanti.\n" "4.0 = L'altezza dell'onda è di due nodi.\n" "0.0 = L'onda non si muove affatto.\n" -"Il valore predefinito è 1.0 (1/2 nodo).\n" -"Richiede l'abilitazione dei liquidi ondulanti." +"Il valore predefinito è 1.0 (1/2 nodo)." #: src/settings_translation_file.cpp #, fuzzy @@ -7334,7 +7294,7 @@ msgid "" msgstr "" "I privilegi ricevuti automaticamente dai nuovi utenti.\n" "Si veda /privs in gioco per un elenco completo sul vostro server e la " -"configurazione dei mod." +"configurazione dei moduli." #: src/settings_translation_file.cpp msgid "" @@ -7458,7 +7418,7 @@ msgid "" "Setting it to -1 disables the feature." msgstr "" "Tempo di vita in secondi per le entità oggetto (oggetti buttati).\n" -"Impostandola a -1 disabilita la caratteristica." +"Imposta a -1 per disabilitare la funzionalità." #: src/settings_translation_file.cpp msgid "Time of day when a new world is started, in millihours (0-23999)." @@ -7473,8 +7433,8 @@ msgstr "Velocità del tempo" #: src/settings_translation_file.cpp msgid "Timeout for client to remove unused map data from memory, in seconds." msgstr "" -"Scadenza per il client per rimuovere dalla memoria dati mappa inutilizzati, " -"in secondi." +"Scadenza per il cliente per rimuovere dalla memoria i dati della mappa " +"inutilizzati, in secondi." #: src/settings_translation_file.cpp msgid "" @@ -7489,72 +7449,64 @@ msgstr "" "rimosso un nodo." #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle Aux1 key" -msgstr "Tasto Speciale" +msgstr "Taso Aux1 sì/no" #: src/settings_translation_file.cpp msgid "Toggle HUD" msgstr "HUD sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle Sneak key" -msgstr "Tasto di (dis)attivazione della modalità telecamera" +msgstr "Furtivo sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle automatic forward" -msgstr "Tasto di avanzamento automatico" +msgstr "Avanzamento automatico sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle block bounds" -msgstr "Limiti del blocco nascosto" +msgstr "Limiti del blocco sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle camera mode" -msgstr "Tasto di (dis)attivazione della modalità telecamera" +msgstr "Cambia inquadratura" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle camera update" -msgstr "Tasto di (dis)attivazione della modalità telecamera" +msgstr "Aggiornamento telecamera sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle cinematic mode" -msgstr "Scegli cinematica" +msgstr "Cinematica sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle debug info" -msgstr "Nebbia sì/no" +msgstr "Mostra informazioni di debug" #: src/settings_translation_file.cpp msgid "Toggle fog" msgstr "Nebbia sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle fullscreen" -msgstr "Volo sì/no" +msgstr "Schermo intero sì/no" #: src/settings_translation_file.cpp msgid "Toggle pitchmove" msgstr "Beccheggio sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle profiler" -msgstr "Volo sì/no" +msgstr "Profilatore sì/no" #: src/settings_translation_file.cpp msgid "" "Tolerance of movement cheat detector.\n" "Increase the value if players experience stuttery movement." msgstr "" +"Tolleranza del rilevatore di cheat di movimento.\n" +"Aumentane il valore se i giocatori vivono un'esperienza a scatti." #: src/settings_translation_file.cpp msgid "Tooltip delay" @@ -7622,7 +7574,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Trusted mods" -msgstr "Mod fidate" +msgstr "Moduli fidati" #: src/settings_translation_file.cpp msgid "" @@ -8010,14 +7962,14 @@ msgid "" msgstr "" "Se lo sfondo dell'etichetta del nome debba essere mostrato per impostazione " "predefinita.\n" -"Le mod possono comunque impostare uno sfondo." +"I moduli possono comunque impostare uno sfondo." #: src/settings_translation_file.cpp msgid "" "Whether players are shown to clients without any range limit.\n" "Deprecated, use the setting player_transfer_distance instead." msgstr "" -"Se i giocatori vengono mostrati ai client senza alcun limite di raggio.\n" +"Se i giocatori vengono mostrati ai clienti senza alcun limite di raggio.\n" "Deprecata, usa invece l'impostazione player_transfer_distance." #: src/settings_translation_file.cpp @@ -8029,7 +7981,7 @@ msgid "" "Whether to ask clients to reconnect after a (Lua) crash.\n" "Set this to true if your server is set up to restart automatically." msgstr "" -"Se chiedere ai client di riconnettersi dopo un crash (Lua).\n" +"Se chiedere ai clienti di riconnettersi dopo un crash (Lua).\n" "Impostatela su Vero se il vostro server è configurato per riavviarsi " "automaticamente." @@ -8038,23 +7990,21 @@ msgid "Whether to fog out the end of the visible area." msgstr "Se annebbiare o meno la fine dell'area visibile." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "Whether to mute sounds. You can unmute sounds at any time.\n" "In-game, you can toggle the mute state with the mute key or by using the\n" "pause menu." msgstr "" "Se silenziare i suoni. È possibile de-silenziare i suoni in qualsiasi " -"momento, a meno che\n" -"il sistema audio non sia disabilitato (enable_sound=false).\n" -"Nel gioco, puoi alternare lo stato silenziato col tasto muta o usando\n" +"momento.\n" +"In partita puoi mutare e smutare il gioco col tasto muta o usando\n" "il menu di pausa." #: src/settings_translation_file.cpp msgid "" "Whether to show the client debug info (has the same effect as hitting F5)." msgstr "" -"Se mostrare le informazioni di debug del client (ha lo stesso effetto di " +"Se mostrare le informazioni di debug del cliente (ha lo stesso effetto di " "premere F5)." #: src/settings_translation_file.cpp @@ -8096,7 +8046,6 @@ msgid "World start time" msgstr "Ora di avvio del mondo" #: src/settings_translation_file.cpp -#, fuzzy msgid "" "World-aligned textures may be scaled to span several nodes. However,\n" "the server may not send the scale you want, especially if you use\n" @@ -8110,7 +8059,7 @@ msgstr "" "Tuttavia, il server potrebbe non inviare le dimensioni desiderate, " "specialmente se è in uso un\n" "pacchetto texture progettato in modo specifico; con questa opzione, il " -"client prova a stabilire\n" +"cliente prova a stabilire\n" "automaticamente le dimensioni basandosi sulla grandezza della texture.\n" "Si veda anche texture_min_size.\n" "Avviso: questa opzione è SPERIMENTALE!" From 1b9a5074a2b85860106ede70d554003c02b30be3 Mon Sep 17 00:00:00 2001 From: Linerly Date: Thu, 15 May 2025 04:13:36 +0200 Subject: [PATCH 10/48] Translated using Weblate (Indonesian) Currently translated at 100.0% (1531 of 1531 strings) --- po/id/luanti.po | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/po/id/luanti.po b/po/id/luanti.po index 2b29b9cc1..801bdf42b 100644 --- a/po/id/luanti.po +++ b/po/id/luanti.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: Indonesian (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-04-25 10:52+0000\n" +"PO-Revision-Date: 2025-05-15 23:01+0000\n" "Last-Translator: Linerly \n" "Language-Team: Indonesian \n" @@ -12,7 +12,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.11.1-dev\n" +"X-Generator: Weblate 5.12-dev\n" #: builtin/client/chatcommands.lua msgid "Clear the out chat queue" @@ -930,29 +930,27 @@ msgstr "Hapus dunia \"$1\"?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Oleh sebab itu, pengikatan tombol kamu dapat berubah." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Periksa pengaturan tombol atau rujuk pada dokumentasi:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Tutup" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Pengikatan tombol" +msgstr "Pengikatan tombol berubah" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Pengaturan" +msgstr "Buka pengaturan" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "Sistem penanganan masukan telah dikerjakan ulang dalam Luanti 5.12.0." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" From 813f67021b824de152c1d2bd120d3cb2edc390fb Mon Sep 17 00:00:00 2001 From: y5nw Date: Thu, 15 May 2025 00:24:35 +0200 Subject: [PATCH 11/48] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 88.7% (1358 of 1531 strings) --- po/zh_CN/luanti.po | 194 ++++++++++++++++----------------------------- 1 file changed, 69 insertions(+), 125 deletions(-) diff --git a/po/zh_CN/luanti.po b/po/zh_CN/luanti.po index 9d85accc8..201169c0a 100644 --- a/po/zh_CN/luanti.po +++ b/po/zh_CN/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: Chinese (Simplified) (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-02-24 16:36+0000\n" -"Last-Translator: BX Zhang \n" +"PO-Revision-Date: 2025-05-15 23:02+0000\n" +"Last-Translator: y5nw \n" "Language-Team: Chinese (Simplified Han script) \n" "Language: zh_CN\n" @@ -12,7 +12,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.10.1-dev\n" +"X-Generator: Weblate 5.12-dev\n" #: builtin/client/chatcommands.lua msgid "Clear the out chat queue" @@ -82,16 +82,15 @@ msgstr "浏览" #: builtin/common/settings/components.lua msgid "Conflicts with \"$1\"" -msgstr "" +msgstr "与“$1”冲突" #: builtin/common/settings/components.lua msgid "Edit" msgstr "编辑" #: builtin/common/settings/components.lua -#, fuzzy msgid "Remove keybinding" -msgstr "按键绑定。" +msgstr "移除按键绑定" #: builtin/common/settings/components.lua msgid "Select directory" @@ -253,7 +252,7 @@ msgstr "通用" #: builtin/common/settings/dlg_settings.lua msgid "Long tap" -msgstr "" +msgstr "长按" #: builtin/common/settings/dlg_settings.lua msgid "Movement" @@ -558,12 +557,11 @@ msgstr "捐赠" #: builtin/mainmenu/content/dlg_package.lua msgid "Error loading package information" -msgstr "" +msgstr "无法获取软件包信息" #: builtin/mainmenu/content/dlg_package.lua -#, fuzzy msgid "Error loading reviews" -msgstr "创建客户端出错:%s" +msgstr "无法加载评价" #: builtin/mainmenu/content/dlg_package.lua msgid "Forum Topic" @@ -583,7 +581,7 @@ msgstr "问题跟踪器" #: builtin/mainmenu/content/dlg_package.lua msgid "Reviews" -msgstr "" +msgstr "评价" #: builtin/mainmenu/content/dlg_package.lua msgid "Source" @@ -638,10 +636,11 @@ msgid "Unable to install a $1 as a texture pack" msgstr "无法将$1安装为材质包" #: builtin/mainmenu/dlg_clients_list.lua +#, fuzzy msgid "" "Players connected to\n" "$1" -msgstr "" +msgstr "连接到$1的玩家" #: builtin/mainmenu/dlg_config_world.lua msgid "(Enabled, has error)" @@ -924,25 +923,23 @@ msgstr "删除世界“$1”?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "您的一些按键绑定有可能已被更改。" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "请查看按键绑定设置或参考文档:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "关闭" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "按键绑定。" +msgstr "按键绑定已被更改" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "设置" +msgstr "打开设置" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." @@ -1292,12 +1289,11 @@ msgid "Ping" msgstr "ping值" #: builtin/mainmenu/tab_online.lua -#, fuzzy msgid "" "Players:\n" "$1" msgstr "" -"客户端:\n" +"玩家::\n" "$1" #: builtin/mainmenu/tab_online.lua @@ -2247,7 +2243,7 @@ msgstr "溢出菜单" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp #, fuzzy msgid "Place/use" -msgstr "放置键" +msgstr "放置或使用" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Range select" @@ -2262,9 +2258,8 @@ msgid "Toggle chat log" msgstr "启用/禁用聊天记录" #: src/gui/touchscreenlayout.cpp -#, fuzzy msgid "Toggle debug" -msgstr "启用/禁用雾" +msgstr "显示/隐藏调试信息" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Toggle fast" @@ -2301,9 +2296,8 @@ msgid "Internal server error" msgstr "内部服务器错误" #: src/network/clientpackethandler.cpp -#, fuzzy msgid "Invalid password" -msgstr "旧密码" +msgstr "密码错误" #. ~ DO NOT TRANSLATE THIS LITERALLY! #. This is a special string which needs to contain the translation's @@ -2977,9 +2971,8 @@ msgid "Client" msgstr "客户端" #: src/settings_translation_file.cpp -#, fuzzy msgid "Client Debugging" -msgstr "调试" +msgstr "客户端调试" #: src/settings_translation_file.cpp msgid "Client Mesh Chunksize" @@ -3233,7 +3226,6 @@ msgid "Decrease view range" msgstr "减少可视范围" #: src/settings_translation_file.cpp -#, fuzzy msgid "Decrease volume" msgstr "减小音量" @@ -3446,9 +3438,8 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Drop item" -msgstr "丢弃物品键" +msgstr "丢弃物品" #: src/settings_translation_file.cpp msgid "Dump the mapgen debug information." @@ -4133,164 +4124,132 @@ msgstr "" "单位为方块每二次方秒。" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 1" -msgstr "快捷栏1键" +msgstr "快捷栏第1项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 10" -msgstr "快捷栏10键" +msgstr "快捷栏第10项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 11" -msgstr "快捷栏11键" +msgstr "快捷栏第11项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 12" -msgstr "快捷栏12键" +msgstr "快捷栏第12项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 13" -msgstr "快捷栏13键" +msgstr "快捷栏第13项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 14" -msgstr "快捷栏14键" +msgstr "快捷栏第14项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 15" -msgstr "快捷栏15键" +msgstr "快捷栏第15项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 16" -msgstr "快捷栏16键" +msgstr "快捷栏第16项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 17" -msgstr "快捷栏17键" +msgstr "快捷栏第17项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 18" -msgstr "快捷栏18键" +msgstr "快捷栏第18项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 19" -msgstr "快捷栏19键" +msgstr "快捷栏第19项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 2" -msgstr "快捷栏2键" +msgstr "快捷栏第2项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 20" -msgstr "快捷栏20键" +msgstr "快捷栏第20项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 21" -msgstr "快捷栏21键" +msgstr "快捷栏第21项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 22" -msgstr "快捷栏22键" +msgstr "快捷栏第22项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 23" -msgstr "快捷栏23键" +msgstr "快捷栏第23项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 24" -msgstr "快捷栏24键" +msgstr "快捷栏第24项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 25" -msgstr "快捷栏25键" +msgstr "快捷栏第25项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 26" -msgstr "快捷栏26键" +msgstr "快捷栏第26项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 27" -msgstr "快捷栏27键" +msgstr "快捷栏第27项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 28" -msgstr "快捷栏28键" +msgstr "快捷栏第28项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 29" -msgstr "快捷栏29键" +msgstr "快捷栏第29项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 3" -msgstr "快捷栏3键" +msgstr "快捷栏第3项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 30" -msgstr "快捷栏30键" +msgstr "快捷栏第30项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 31" -msgstr "快捷栏31键" +msgstr "快捷栏第31项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 32" -msgstr "快捷栏32键" +msgstr "快捷栏第32项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 4" -msgstr "快捷栏4键" +msgstr "快捷栏第4项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 5" -msgstr "快捷栏5键" +msgstr "快捷栏第5项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 6" -msgstr "快捷栏6键" +msgstr "快捷栏第6项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 7" -msgstr "快捷栏7键" +msgstr "快捷栏第7项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 8" -msgstr "快捷栏8键" +msgstr "快捷栏第8项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 9" -msgstr "快捷栏9键" +msgstr "快捷栏第9项" #: src/settings_translation_file.cpp msgid "Hotbar: Enable mouse wheel for selection" @@ -4301,14 +4260,12 @@ msgid "Hotbar: Invert mouse wheel direction" msgstr "快捷栏:反转鼠标滚轮方向" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar: select next item" -msgstr "快捷栏下一个键" +msgstr "选择快捷栏下一项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar: select previous item" -msgstr "快捷栏上一个键" +msgstr "选择快捷栏上一项" #: src/settings_translation_file.cpp msgid "How deep to make rivers." @@ -4513,7 +4470,6 @@ msgid "Increase view range" msgstr "增加可视范围" #: src/settings_translation_file.cpp -#, fuzzy msgid "Increase volume" msgstr "增大音量" @@ -5031,9 +4987,8 @@ msgid "Key to use view zoom when possible." msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Keybindings" -msgstr "按键绑定。" +msgstr "按键绑定" #: src/settings_translation_file.cpp msgid "Keyboard and Mouse" @@ -5072,9 +5027,8 @@ msgid "Large cave proportion flooded" msgstr "大型洞穴淹没比" #: src/settings_translation_file.cpp -#, fuzzy msgid "Large chat console" -msgstr "大型聊天控制台键" +msgstr "大型聊天控制台" #: src/settings_translation_file.cpp msgid "Leaves style" @@ -5714,23 +5668,20 @@ msgid "Mouse sensitivity multiplier." msgstr "鼠标灵敏度倍数。" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move backward" -msgstr "向后" +msgstr "后退" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move forward" -msgstr "自动向前" +msgstr "前进" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move left" -msgstr "移动" +msgstr "往左移动" #: src/settings_translation_file.cpp msgid "Move right" -msgstr "" +msgstr "往右移动" #: src/settings_translation_file.cpp #, fuzzy @@ -5879,14 +5830,12 @@ msgid "" msgstr "默认字体后阴影的透明度(alpha),取值范围0~255。" #: src/settings_translation_file.cpp -#, fuzzy msgid "Open chat" -msgstr "打开" +msgstr "打开聊天室" #: src/settings_translation_file.cpp -#, fuzzy msgid "Open inventory" -msgstr "物品栏" +msgstr "打开物品栏" #: src/settings_translation_file.cpp msgid "" @@ -7174,14 +7123,12 @@ msgid "Toggle Sneak key" msgstr "启用/禁用拍照模式键" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle automatic forward" -msgstr "自动前进键" +msgstr "启用/禁用自动前进" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle block bounds" -msgstr "地图块边界" +msgstr "显示/隐藏地图块边界" #: src/settings_translation_file.cpp #, fuzzy @@ -7199,18 +7146,16 @@ msgid "Toggle cinematic mode" msgstr "切换电影模式" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle debug info" -msgstr "启用/禁用雾" +msgstr "显示/隐藏调试信息" #: src/settings_translation_file.cpp msgid "Toggle fog" msgstr "启用/禁用雾" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle fullscreen" -msgstr "启用/禁用飞行模式" +msgstr "切换全屏模式" #: src/settings_translation_file.cpp msgid "Toggle pitchmove" @@ -7238,9 +7183,8 @@ msgid "Touchscreen" msgstr "触摸屏" #: src/settings_translation_file.cpp -#, fuzzy msgid "Touchscreen controls" -msgstr "触屏阈值" +msgstr "触屏控制" #: src/settings_translation_file.cpp msgid "Touchscreen sensitivity" From f6c933d891e0bc8f4e794c7d4d4898513b4f91d5 Mon Sep 17 00:00:00 2001 From: BlackImpostor Date: Fri, 16 May 2025 13:20:49 +0200 Subject: [PATCH 12/48] Translated using Weblate (Russian) Currently translated at 100.0% (1531 of 1531 strings) --- po/ru/luanti.po | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/po/ru/luanti.po b/po/ru/luanti.po index a9109e95d..c31366bc0 100644 --- a/po/ru/luanti.po +++ b/po/ru/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: Russian (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-05-03 05:01+0000\n" -"Last-Translator: Nana_M \n" +"PO-Revision-Date: 2025-05-16 16:16+0000\n" +"Last-Translator: BlackImpostor \n" "Language-Team: Russian \n" "Language: ru\n" @@ -934,29 +934,27 @@ msgstr "Удалить мир «$1»?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "В результате ваши привязки клавиш, возможно, были изменены." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Ознакомьтесь с настройками клавиш или обратитесь к документации:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Закрыть" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Привязки клавиш" +msgstr "Изменены привязки клавиш" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Настройки" +msgstr "Открыть настройки" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "Система обработки входных данных была переработана в Luanti 5.12.0." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" @@ -2250,7 +2248,7 @@ msgstr "Бросить" #: src/gui/touchscreenlayout.cpp msgid "Exit" -msgstr "Закрыть" +msgstr "Выйти" #: src/gui/touchscreenlayout.cpp msgid "Inventory" From db561ff094fb3577de4dc9c0ea9137b364cf5509 Mon Sep 17 00:00:00 2001 From: Ian Pedras Date: Sun, 18 May 2025 11:55:00 +0200 Subject: [PATCH 13/48] Translated using Weblate (Portuguese) Currently translated at 80.6% (1235 of 1531 strings) --- po/pt/luanti.po | 86 ++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/po/pt/luanti.po b/po/pt/luanti.po index 86a3f1344..974f76346 100644 --- a/po/pt/luanti.po +++ b/po/pt/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: Portuguese (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-05-12 16:41+0000\n" -"Last-Translator: Felipe Amaral \n" +"PO-Revision-Date: 2025-05-18 15:01+0000\n" +"Last-Translator: Ian Pedras \n" "Language-Team: Portuguese \n" "Language: pt\n" @@ -934,30 +934,29 @@ msgid "Delete World \"$1\"?" msgstr "Eliminar mundo \"$1\"?" #: builtin/mainmenu/dlg_rebind_keys.lua +#, fuzzy msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Como resultado, os seus comandos foram mudados." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Veja as configurações ou refera a decumentação:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Fechar" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Combinações de teclas." +msgstr "Combinações de teclas mudadas" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Definições" +msgstr "Abrir Definições" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "O sistema de combinações de teclas foi remodelado no Luanti 5.12.0." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" @@ -2058,7 +2057,7 @@ msgstr "Scroll Lock" #. ~ Key name #: src/client/keycode.cpp msgid "Select" -msgstr "Seleccionar" +msgstr "Selecionar" #: src/client/keycode.cpp msgid "Shift Key" @@ -2115,14 +2114,14 @@ msgid "Minimap in texture mode" msgstr "Minimapa em modo de textura" #: src/client/shader.cpp -#, fuzzy, c-format +#, c-format msgid "Failed to compile the \"%s\" shader." -msgstr "Falha ao abrir página da web" +msgstr "Falha ao compilar o \"%s\" shader." #: src/client/shader.cpp #, fuzzy msgid "GLSL is not supported by the driver" -msgstr "Som do sistema não é suportado nesta versão" +msgstr "GLSL não é suportado pelo seu sistema" #. ~ Error when a mod is missing dependencies. Ex: "Mod Title is missing: mod1, mod2, mod3" #: src/content/mod_configuration.cpp @@ -2168,11 +2167,11 @@ msgstr "Continuar" #: src/gui/guiOpenURL.cpp msgid "Open" -msgstr "" +msgstr "Abrir" #: src/gui/guiOpenURL.cpp msgid "Open URL?" -msgstr "" +msgstr "Abrir URL?" #: src/gui/guiOpenURL.cpp #, fuzzy @@ -2207,33 +2206,34 @@ msgstr "Volume do som: %d%%" #: src/gui/touchscreeneditor.cpp #, fuzzy msgid "Add button" -msgstr "Roda do Rato" +msgstr "Adicionar botão" #: src/gui/touchscreeneditor.cpp -#, fuzzy msgid "Done" -msgstr "Feito!" +msgstr "Feito" #: src/gui/touchscreeneditor.cpp #, fuzzy msgid "Remove" -msgstr "Servidor remoto" +msgstr "Remover" #: src/gui/touchscreeneditor.cpp +#, fuzzy msgid "Reset" -msgstr "" +msgstr "Refazer" #: src/gui/touchscreeneditor.cpp +#, fuzzy msgid "Start dragging a button to add. Tap outside to cancel." -msgstr "" +msgstr "Começe a arrastar um botão para adicionar. Clique fora para cancelar." #: src/gui/touchscreeneditor.cpp msgid "Tap a button to select it. Drag a button to move it." -msgstr "" +msgstr "Clique num botão para selecioná-lo. Arraste o botão para movê-lo." #: src/gui/touchscreeneditor.cpp msgid "Tap outside to deselect." -msgstr "" +msgstr "Click fora para desselecionar." #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Aux1" @@ -2245,7 +2245,7 @@ msgstr "Mudar camera" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Dig/punch/use" -msgstr "" +msgstr "Escavar/Attacar/Usar" #: src/gui/touchscreenlayout.cpp msgid "Drop" @@ -2370,27 +2370,32 @@ msgstr "" #: src/network/clientpackethandler.cpp msgid "The server is running in singleplayer mode. You cannot connect." -msgstr "" +msgstr "Este servidor está a correr no modo uni jogador. Não se pode connectar." #: src/network/clientpackethandler.cpp msgid "Too many users" -msgstr "" +msgstr "Demasiados utilizadores" #: src/network/clientpackethandler.cpp msgid "Unknown disconnect reason." -msgstr "" +msgstr "Razão de desconecto desconhecido." #: src/network/clientpackethandler.cpp +#, fuzzy msgid "" "Your client sent something the server didn't expect. Try reconnecting or " "updating your client." msgstr "" +"O seu cliente mandou algo que o servidor não esperava. Tente reconectar ou " +"atualizar o seu cliente." #: src/network/clientpackethandler.cpp msgid "" "Your client's version is not supported.\n" "Please contact the server administrator." msgstr "" +"A versão do seu cliente não é supportado.\n" +"Por favor contacte o administrador do servidor." #: src/server.cpp #, c-format @@ -2465,7 +2470,7 @@ msgstr "Ruído 2D que localiza os vales e canais dos rios." #: src/settings_translation_file.cpp msgid "3D" -msgstr "" +msgstr "3D" #: src/settings_translation_file.cpp msgid "3D clouds" @@ -2638,13 +2643,13 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Allow clouds to look 3D instead of flat." msgstr "Usar nuvens 3D em vez de planas." #: src/settings_translation_file.cpp +#, fuzzy msgid "Allows liquids to be translucent." -msgstr "" +msgstr "Permitir liquidos semi transparentes." #: src/settings_translation_file.cpp msgid "" @@ -2697,8 +2702,9 @@ msgid "Anticheat flags" msgstr "" #: src/settings_translation_file.cpp +#, fuzzy msgid "Anticheat movement tolerance" -msgstr "" +msgstr "Tolerância de movimentos anti-batota" #: src/settings_translation_file.cpp msgid "Append item name" @@ -2724,8 +2730,9 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp +#, fuzzy msgid "Apply specular shading to nodes." -msgstr "" +msgstr "Aplicar shading especular aos nodes." #: src/settings_translation_file.cpp msgid "Arm inertia" @@ -2824,9 +2831,8 @@ msgid "Base terrain height." msgstr "Altura base do terreno." #: src/settings_translation_file.cpp -#, fuzzy msgid "Base texture size" -msgstr "Tamanho mínimo da textura" +msgstr "Tamanho base da textura" #: src/settings_translation_file.cpp msgid "Basic privileges" @@ -2849,9 +2855,8 @@ msgid "Bind address" msgstr "Endereço de bind" #: src/settings_translation_file.cpp -#, fuzzy msgid "Biome API" -msgstr "Biomas" +msgstr "API de Biomas" #: src/settings_translation_file.cpp msgid "Biome noise" @@ -3016,13 +3021,12 @@ msgid "Client" msgstr "Cliente" #: src/settings_translation_file.cpp -#, fuzzy msgid "Client Debugging" -msgstr "Debugging" +msgstr "Debugging de cliente" #: src/settings_translation_file.cpp msgid "Client Mesh Chunksize" -msgstr "" +msgstr "Tamhãnho do Client Mesh Chunksize" #: src/settings_translation_file.cpp msgid "Client and Server" @@ -3063,7 +3067,7 @@ msgstr "Nuvens no menu" #: src/settings_translation_file.cpp msgid "Color depth for post-processing texture" -msgstr "" +msgstr "Profundidade da cor para a textura post-processing" #: src/settings_translation_file.cpp msgid "Colored fog" From fc1d57b6664ba65c21362db160fce3e9ed07e172 Mon Sep 17 00:00:00 2001 From: waxtatect Date: Sun, 18 May 2025 19:19:47 +0200 Subject: [PATCH 14/48] Translated using Weblate (French) Currently translated at 100.0% (1531 of 1531 strings) --- po/fr/luanti.po | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/po/fr/luanti.po b/po/fr/luanti.po index fa6fa8839..089ba13c3 100644 --- a/po/fr/luanti.po +++ b/po/fr/luanti.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: French (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-04-28 18:58+0000\n" +"PO-Revision-Date: 2025-05-18 17:31+0000\n" "Last-Translator: waxtatect \n" "Language-Team: French \n" @@ -938,29 +938,27 @@ msgstr "Supprimer le monde « $1 » ?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Ainsi, il est possible que certaines touches aient été modifiées." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Vérifier les paramètres des touches ou consulter la documentation :" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Fermer" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Raccourcis clavier" +msgstr "Modification des raccourcis clavier" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Paramètres" +msgstr "Ouvrir les paramètres" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "Le système d'affectation des touches a été remanié dans Luanti 5.12.0." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" From b459d6ee6352a7194c5d2f01cfe2ef0dfd1d1f96 Mon Sep 17 00:00:00 2001 From: Josu Igoa Date: Wed, 21 May 2025 11:19:48 +0200 Subject: [PATCH 15/48] Translated using Weblate (Basque) Currently translated at 21.0% (323 of 1531 strings) --- po/eu/luanti.po | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/po/eu/luanti.po b/po/eu/luanti.po index 39e391e21..5e8dc52e7 100644 --- a/po/eu/luanti.po +++ b/po/eu/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: minetest\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2022-04-29 20:12+0000\n" -"Last-Translator: JonAnder Oier \n" +"PO-Revision-Date: 2025-05-21 10:48+0000\n" +"Last-Translator: Josu Igoa \n" "Language-Team: Basque \n" "Language: eu\n" @@ -12,7 +12,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.12.1\n" +"X-Generator: Weblate 5.12-dev\n" #: builtin/client/chatcommands.lua msgid "Clear the out chat queue" @@ -23,9 +23,8 @@ msgid "Empty command." msgstr "Agindu hutsa." #: builtin/client/chatcommands.lua -#, fuzzy msgid "Exit to main menu" -msgstr "Itzuli menu nagusira" +msgstr "Irten menu nagusira" #: builtin/client/chatcommands.lua msgid "Invalid command: " @@ -64,9 +63,8 @@ msgid "Command not available: " msgstr "Komandoa ez dago eskuragarri: " #: builtin/common/chatcommands.lua -#, fuzzy msgid "Get help for commands (-t: output in chat)" -msgstr "Eskuratu laguntza komandoetarako" +msgstr "Eskuratu laguntza komandoetarako (-t: irteera txat-ean)" #: builtin/common/chatcommands.lua msgid "" @@ -76,9 +74,8 @@ msgstr "" "zerrendatzeko." #: builtin/common/chatcommands.lua -#, fuzzy msgid "[all | ] [-t]" -msgstr "[guztia | ]" +msgstr "[all | ] [-t]" #: builtin/common/settings/components.lua msgid "Browse" From 9b2aeb2ca27fe67ae261596a4fcd2dd7d4dbcd14 Mon Sep 17 00:00:00 2001 From: "updatepo.sh" Date: Fri, 23 May 2025 17:09:44 +0200 Subject: [PATCH 16/48] Update minetest.conf.example --- minetest.conf.example | 240 +++++++++++++++++++++--------------------- 1 file changed, 118 insertions(+), 122 deletions(-) diff --git a/minetest.conf.example b/minetest.conf.example index d9b7a58ff..29cbb207b 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -95,366 +95,366 @@ ### Keybindings # Key for moving the player forward. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_forward = KEY_KEY_W +# keymap_forward = SYSTEM_SCANCODE_26 # Key for moving the player backward. # Will also disable autoforward, when active. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_backward = KEY_KEY_S +# keymap_backward = SYSTEM_SCANCODE_22 # Key for moving the player left. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_left = KEY_KEY_A +# keymap_left = SYSTEM_SCANCODE_4 # Key for moving the player right. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_right = KEY_KEY_D +# keymap_right = SYSTEM_SCANCODE_7 # Key for jumping. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_jump = KEY_SPACE +# keymap_jump = SYSTEM_SCANCODE_44 # Key for sneaking. # Also used for climbing down and descending in water if aux1_descends is disabled. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_sneak = KEY_LSHIFT +# keymap_sneak = SYSTEM_SCANCODE_225 # Key for digging, punching or using something. # (Note: The actual meaning might vary on a per-game basis.) -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_dig = KEY_LBUTTON # Key for placing an item/block or for using something. # (Note: The actual meaning might vary on a per-game basis.) -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_place = KEY_RBUTTON # Key for opening the inventory. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_inventory = KEY_KEY_I +# keymap_inventory = SYSTEM_SCANCODE_12 # Key for moving fast in fast mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_aux1 = KEY_KEY_E +# keymap_aux1 = SYSTEM_SCANCODE_8 # Key for opening the chat window. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_chat = KEY_KEY_T +# keymap_chat = SYSTEM_SCANCODE_23 # Key for opening the chat window to type commands. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_cmd = / +# keymap_cmd = SYSTEM_SCANCODE_56 # Key for opening the chat window to type local commands. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_cmd_local = . +# keymap_cmd_local = SYSTEM_SCANCODE_55 # Key for toggling unlimited view range. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_rangeselect = # Key for toggling flying. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_freemove = KEY_KEY_K +# keymap_freemove = SYSTEM_SCANCODE_14 # Key for toggling pitch move mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_pitchmove = # Key for toggling fast mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_fastmove = KEY_KEY_J +# keymap_fastmove = SYSTEM_SCANCODE_13 # Key for toggling noclip mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_noclip = KEY_KEY_H +# keymap_noclip = SYSTEM_SCANCODE_11 # Key for selecting the next item in the hotbar. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_hotbar_next = KEY_KEY_N +# keymap_hotbar_next = SYSTEM_SCANCODE_17 # Key for selecting the previous item in the hotbar. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_hotbar_previous = KEY_KEY_B +# keymap_hotbar_previous = SYSTEM_SCANCODE_5 # Key for muting the game. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_mute = KEY_KEY_M +# keymap_mute = SYSTEM_SCANCODE_16 # Key for increasing the volume. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_increase_volume = # Key for decreasing the volume. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_decrease_volume = # Key for toggling autoforward. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_autoforward = # Key for toggling cinematic mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_cinematic = # Key for toggling display of minimap. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_minimap = KEY_KEY_V +# keymap_minimap = SYSTEM_SCANCODE_25 # Key for taking screenshots. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_screenshot = KEY_F12 +# keymap_screenshot = SYSTEM_SCANCODE_69 # Key for toggling fullscreen mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_fullscreen = KEY_F11 +# keymap_fullscreen = SYSTEM_SCANCODE_68 # Key for dropping the currently selected item. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_drop = KEY_KEY_Q +# keymap_drop = SYSTEM_SCANCODE_20 # Key to use view zoom when possible. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_zoom = KEY_KEY_Z +# keymap_zoom = SYSTEM_SCANCODE_29 # Key for toggling the display of the HUD. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_hud = KEY_F1 +# keymap_toggle_hud = SYSTEM_SCANCODE_58 # Key for toggling the display of chat. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_chat = KEY_F2 +# keymap_toggle_chat = SYSTEM_SCANCODE_59 # Key for toggling the display of the large chat console. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_console = KEY_F10 +# keymap_console = SYSTEM_SCANCODE_67 # Key for toggling the display of fog. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_fog = KEY_F3 +# keymap_toggle_fog = SYSTEM_SCANCODE_60 # Key for toggling the display of debug info. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_debug = KEY_F5 +# keymap_toggle_debug = SYSTEM_SCANCODE_62 # Key for toggling the display of the profiler. Used for development. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_profiler = KEY_F6 +# keymap_toggle_profiler = SYSTEM_SCANCODE_63 # Key for toggling the display of mapblock boundaries. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_toggle_block_bounds = # Key for switching between first- and third-person camera. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_camera_mode = KEY_KEY_C +# keymap_camera_mode = SYSTEM_SCANCODE_6 # Key for increasing the viewing range. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_increase_viewing_range_min = + +# keymap_increase_viewing_range_min = SYSTEM_SCANCODE_46 # Key for decreasing the viewing range. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_decrease_viewing_range_min = - +# keymap_decrease_viewing_range_min = SYSTEM_SCANCODE_45 # Key for selecting the first hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot1 = KEY_KEY_1 +# keymap_slot1 = SYSTEM_SCANCODE_30 # Key for selecting the second hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot2 = KEY_KEY_2 +# keymap_slot2 = SYSTEM_SCANCODE_31 # Key for selecting the third hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot3 = KEY_KEY_3 +# keymap_slot3 = SYSTEM_SCANCODE_32 # Key for selecting the fourth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot4 = KEY_KEY_4 +# keymap_slot4 = SYSTEM_SCANCODE_33 # Key for selecting the fifth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot5 = KEY_KEY_5 +# keymap_slot5 = SYSTEM_SCANCODE_34 # Key for selecting the sixth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot6 = KEY_KEY_6 +# keymap_slot6 = SYSTEM_SCANCODE_35 # Key for selecting the seventh hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot7 = KEY_KEY_7 +# keymap_slot7 = SYSTEM_SCANCODE_36 # Key for selecting the eighth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot8 = KEY_KEY_8 +# keymap_slot8 = SYSTEM_SCANCODE_37 # Key for selecting the ninth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot9 = KEY_KEY_9 +# keymap_slot9 = SYSTEM_SCANCODE_38 # Key for selecting the tenth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot10 = KEY_KEY_0 +# keymap_slot10 = SYSTEM_SCANCODE_39 # Key for selecting the 11th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot11 = # Key for selecting the 12th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot12 = # Key for selecting the 13th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot13 = # Key for selecting the 14th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot14 = # Key for selecting the 15th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot15 = # Key for selecting the 16th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot16 = # Key for selecting the 17th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot17 = # Key for selecting the 18th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot18 = # Key for selecting the 19th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot19 = # Key for selecting the 20th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot20 = # Key for selecting the 21st hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot21 = # Key for selecting the 22nd hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot22 = # Key for selecting the 23rd hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot23 = # Key for selecting the 24th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot24 = # Key for selecting the 25th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot25 = # Key for selecting the 26th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot26 = # Key for selecting the 27th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot27 = # Key for selecting the 28th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot28 = # Key for selecting the 29th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot29 = # Key for selecting the 30th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot30 = # Key for selecting the 31st hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot31 = # Key for selecting the 32nd hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot32 = @@ -925,10 +925,6 @@ # type: bool # enable_translucent_foliage = false -# Apply specular shading to nodes. -# type: bool -# enable_node_specular = false - # When enabled, liquid reflections are simulated. # type: bool # enable_water_reflections = false @@ -3554,27 +3550,27 @@ ### Client Debugging # Key for toggling the camera update. Only usable with 'debug' privilege. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_toggle_update_camera = # Key for switching to the previous entry in Quicktune. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_quicktune_prev = # Key for switching to the next entry in Quicktune. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_quicktune_next = # Key for decrementing the selected value in Quicktune. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_quicktune_dec = # Key for incrementing the selected value in Quicktune. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_quicktune_inc = From 8f0838506a83c108ff85f18050ced4f00a51950b Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 23 May 2025 17:43:08 +0200 Subject: [PATCH 17/48] Bump version to 5.12.0 --- CMakeLists.txt | 2 +- misc/org.luanti.luanti.metainfo.xml | 2 +- util/bump_version.sh | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bf2effd1..8977af474 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ set(VERSION_PATCH 0) set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") # Change to false for releases -set(DEVELOPMENT_BUILD TRUE) +set(DEVELOPMENT_BUILD FALSE) set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") if(VERSION_EXTRA) diff --git a/misc/org.luanti.luanti.metainfo.xml b/misc/org.luanti.luanti.metainfo.xml index 0c5b1a717..3e99aa36b 100644 --- a/misc/org.luanti.luanti.metainfo.xml +++ b/misc/org.luanti.luanti.metainfo.xml @@ -174,6 +174,6 @@ celeron55@gmail.com - + diff --git a/util/bump_version.sh b/util/bump_version.sh index 77b4e603b..0fd4f3ae1 100755 --- a/util/bump_version.sh +++ b/util/bump_version.sh @@ -124,10 +124,10 @@ perform_release() { local release_version=$1 RELEASE_DATE=$(date +%Y-%m-%d) - sed -i '/\ Date: Fri, 23 May 2025 17:43:09 +0200 Subject: [PATCH 18/48] Continue with 5.13.0-dev --- CMakeLists.txt | 4 ++-- android/build.gradle | 2 +- doc/client_lua_api.md | 2 +- doc/menu_lua_api.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8977af474..70a027f57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,12 +14,12 @@ set(CLANG_MINIMUM_VERSION "7.0.1") # You should not need to edit these manually, use util/bump_version.sh set(VERSION_MAJOR 5) -set(VERSION_MINOR 12) +set(VERSION_MINOR 13) set(VERSION_PATCH 0) set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") # Change to false for releases -set(DEVELOPMENT_BUILD FALSE) +set(DEVELOPMENT_BUILD TRUE) set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") if(VERSION_EXTRA) diff --git a/android/build.gradle b/android/build.gradle index 61637c2ec..d2a3b3b2f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,7 +1,7 @@ // 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", 12) // Version Minor +project.ext.set("versionMinor", 13) // Version Minor project.ext.set("versionPatch", 0) // Version Patch // ^ keep in sync with cmake diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index aedf0a4ff..017f8b89b 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -1,4 +1,4 @@ -Luanti Lua Client Modding API Reference 5.12.0 +Luanti Lua Client Modding API Reference 5.13.0 ============================================== **WARNING**: if you're looking for the `minetest` namespace (e.g. `minetest.something`), diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index c0dcc9068..0a067764e 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -1,4 +1,4 @@ -Luanti Lua Mainmenu API Reference 5.12.0 +Luanti Lua Mainmenu API Reference 5.13.0 ======================================== Introduction From 2f1171e2a7dd09775a0c7c6898da9f18e32ba6ab Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 24 May 2025 15:58:04 +0200 Subject: [PATCH 19/48] Formspec: Fix broken 9-slice image button with gui_scaling_filter (#16146) The setting 'gui_scaling_filter = true' previously broke 9-slice images. With this change, custom button background images now scale the same as backgrounds created using 'background9[...]' (9-slice images). --- src/gui/guiButton.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index 9592ba922..9975549fe 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -718,19 +718,24 @@ void GUIButton::setFromStyle(const StyleSpec& style) setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true)); setOverrideFont(style.getFont()); + BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle); + if (style.isNotDefault(StyleSpec::BGIMG)) { video::ITexture *texture = style.getTexture(StyleSpec::BGIMG, getTextureSource()); - setImage(guiScalingImageButton( - Environment->getVideoDriver(), texture, - AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); + if (BgMiddle.getArea() == 0) { + setImage(guiScalingImageButton( + Environment->getVideoDriver(), texture, + AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); + } else { + // Scaling happens in `draw2DImage9Slice` + setImage(texture); + } setScaleImage(true); } else { setImage(nullptr); } - BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle); - // Child padding and offset Padding = style.getRect(StyleSpec::PADDING, core::rect()); Padding = core::rect( From d17f22f536cf5a94dda88d4f36b1faf0bb44958e Mon Sep 17 00:00:00 2001 From: cx384 Date: Sat, 24 May 2025 15:59:32 +0200 Subject: [PATCH 20/48] Fix texture coordinates of cuboid drawtypes (#16091) Fixes issues related to combining animated and world-aligned textures. Changes texture coordinates of cuboid drawtypes to stay in the [0,1] range, instead of carrying the mapblock alignment and becoming negative after transformations. --- src/client/content_mapblock.cpp | 111 +++++++++++++++++++++---------- src/client/content_mapblock.h | 2 +- src/client/meshgen/collector.cpp | 12 +--- src/client/meshgen/collector.h | 2 +- 4 files changed, 80 insertions(+), 47 deletions(-) diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 3edba95e3..c07a0a3b1 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -137,11 +137,20 @@ void MapblockMeshGenerator::drawQuad(const TileSpec &tile, v3f *coords, const v3 } static std::array setupCuboidVertices(const aabb3f &box, - const f32 *txc, const TileSpec *tiles, int tilecount) + const f32 *txc, const TileSpec *tiles, int tilecount, v3s16 alignment) { v3f min = box.MinEdge; v3f max = box.MaxEdge; + // Texture coords are [0,1] if not specified otherwise + f32 uniform_txc[24]; + if (!txc) { + for (int i = 0; i != 24; ++i) { + uniform_txc[i] = (i % 4 < 2) ? 0.0f : 1.0f; + } + txc = uniform_txc; + } + std::array vertices = {{ // top video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, {}, txc[0], txc[1]), @@ -185,15 +194,47 @@ static std::array setupCuboidVertices(const aabb3f &box, case TileRotation::None: break; case TileRotation::R90: - tcoords.set(-tcoords.Y, tcoords.X); + tcoords.set(1 - tcoords.Y, tcoords.X); break; case TileRotation::R180: - tcoords.set(-tcoords.X, -tcoords.Y); + tcoords.set(1 - tcoords.X, 1 - tcoords.Y); break; case TileRotation::R270: - tcoords.set(tcoords.Y, -tcoords.X); + tcoords.set(tcoords.Y, 1 - tcoords.X); break; } + + if (tile.world_aligned) { + // Maps uv dimension of every face to world dimension xyz + constexpr int coord_dim[12] = { + 0, 2, // up + 0, 2, // down + 2, 1, // right + 2, 1, // left + 0, 1, // back + 0, 1, // front + }; + + auto scale = tile.layers[0].scale; + f32 scale_factor = 1.0f / scale; + + float x = alignment[coord_dim[face*2]] % scale; + float y = alignment[coord_dim[face*2 + 1]] % scale; + + // Faces grow in different directions + if (face != 1) { + y = tcoords.Y + ((scale-1)-y); + } else { + y = tcoords.Y + y; + } + if (face == 3 || face == 4) { + x = tcoords.X + ((scale-1)-x); + } else { + x = tcoords.X + x; + } + + tcoords.set(x * scale_factor, y * scale_factor); + } } } @@ -212,6 +253,7 @@ enum class QuadDiagonal { // for the opposite corners of each face - therefore, there // should be (2+2)*6=24 values in the list. The order of // the faces in the list is up-down-right-left-back-front +// if nullptr use standard [0,1] coords // (compatible with ContentFeatures). // mask - a bit mask that suppresses drawing of tiles. // tile i will not be drawn if mask & (1 << i) is 1 @@ -224,7 +266,7 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box, { assert(tilecount >= 1 && tilecount <= 6); // pre-condition - auto vertices = setupCuboidVertices(box, txc, tiles, tilecount); + auto vertices = setupCuboidVertices(box, txc, tiles, tilecount, cur_node.p); for (int k = 0; k < 6; ++k) { if (mask & (1 << k)) @@ -301,12 +343,13 @@ video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos, void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords) { - f32 tx1 = (box.MinEdge.X / BS) + 0.5; - f32 ty1 = (box.MinEdge.Y / BS) + 0.5; - f32 tz1 = (box.MinEdge.Z / BS) + 0.5; - f32 tx2 = (box.MaxEdge.X / BS) + 0.5; - f32 ty2 = (box.MaxEdge.Y / BS) + 0.5; - f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; + // Generate texture coords which are aligned to coords of a solid nodes + f32 tx1 = (box.MinEdge.X / BS) + 0.5f; + f32 ty1 = (box.MinEdge.Y / BS) + 0.5f; + f32 tz1 = (box.MinEdge.Z / BS) + 0.5f; + f32 tx2 = (box.MaxEdge.X / BS) + 0.5f; + f32 ty2 = (box.MaxEdge.Y / BS) + 0.5f; + f32 tz2 = (box.MaxEdge.Z / BS) + 0.5f; f32 txc[24] = { tx1, 1 - tz2, tx2, 1 - tz1, // up tx1, tz1, tx2, tz2, // down @@ -334,7 +377,6 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const TileSpec *tiles, int tile_count, const f32 *txc, u8 mask) { bool scale = std::fabs(cur_node.f->visual_scale - 1.0f) > 1e-3f; - f32 texture_coord_buf[24]; f32 dx1 = box.MinEdge.X; f32 dy1 = box.MinEdge.Y; f32 dz1 = box.MinEdge.Z; @@ -342,19 +384,11 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, f32 dy2 = box.MaxEdge.Y; f32 dz2 = box.MaxEdge.Z; if (scale) { - if (!txc) { // generate texture coords before scaling - generateCuboidTextureCoords(box, texture_coord_buf); - txc = texture_coord_buf; - } box.MinEdge *= cur_node.f->visual_scale; box.MaxEdge *= cur_node.f->visual_scale; } box.MinEdge += cur_node.origin; box.MaxEdge += cur_node.origin; - if (!txc) { - generateCuboidTextureCoords(box, texture_coord_buf); - txc = texture_coord_buf; - } if (data->m_smooth_lighting) { LightInfo lights[8]; for (int j = 0; j < 8; ++j) { @@ -442,10 +476,8 @@ void MapblockMeshGenerator::drawSolidNode() u8 mask = faces ^ 0b0011'1111; // k-th bit is set if k-th face is to be *omitted*, as expected by cuboid drawing functions. cur_node.origin = intToFloat(cur_node.p, BS); auto box = aabb3f(v3f(-0.5 * BS), v3f(0.5 * BS)); - f32 texture_coord_buf[24]; box.MinEdge += cur_node.origin; box.MaxEdge += cur_node.origin; - generateCuboidTextureCoords(box, texture_coord_buf); if (data->m_smooth_lighting) { LightPair lights[6][4]; for (int face = 0; face < 6; ++face) { @@ -458,7 +490,7 @@ void MapblockMeshGenerator::drawSolidNode() } } - drawCuboid(box, tiles, 6, texture_coord_buf, mask, [&] (int face, video::S3DVertex vertices[4]) { + drawCuboid(box, tiles, 6, nullptr, mask, [&] (int face, video::S3DVertex vertices[4]) { auto final_lights = lights[face]; for (int j = 0; j < 4; j++) { video::S3DVertex &vertex = vertices[j]; @@ -471,7 +503,7 @@ void MapblockMeshGenerator::drawSolidNode() return QuadDiagonal::Diag02; }); } else { - drawCuboid(box, tiles, 6, texture_coord_buf, mask, [&] (int face, video::S3DVertex vertices[4]) { + drawCuboid(box, tiles, 6, nullptr, mask, [&] (int face, video::S3DVertex vertices[4]) { video::SColor color = encode_light(lights[face], cur_node.f->light_source); if (!cur_node.f->light_source) applyFacesShading(color, vertices[0].Normal); @@ -952,7 +984,10 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]]; if (edge_invisible) continue; - drawAutoLightedCuboid(frame_edges[edge], tiles[1]); + + f32 txc[24]; + generateCuboidTextureCoords(frame_edges[edge], txc); + drawAutoLightedCuboid(frame_edges[edge], tiles[1], txc); } for (int face = 0; face < 6; face++) { @@ -996,16 +1031,17 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() float vlev = (param2 / 63.0f) * 2.0f - 1.0f; TileSpec tile; getSpecialTile(0, &tile); - drawAutoLightedCuboid( - aabb3f( - -(nb[5] ? g : b), - -(nb[4] ? g : b), - -(nb[3] ? g : b), - (nb[2] ? g : b), - (nb[1] ? g : b) * vlev, - (nb[0] ? g : b) - ), - tile); + aabb3f box( + -(nb[5] ? g : b), + -(nb[4] ? g : b), + -(nb[3] ? g : b), + (nb[2] ? g : b), + (nb[1] ? g : b) * vlev, + (nb[0] ? g : b) + ); + f32 txc[24]; + generateCuboidTextureCoords(box, txc); + drawAutoLightedCuboid(box, tile, txc); } } @@ -1649,7 +1685,10 @@ void MapblockMeshGenerator::drawNodeboxNode() for (auto &box : boxes) { u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors); - drawAutoLightedCuboid(box, tiles, 6, nullptr, mask); + + f32 txc[24]; + generateCuboidTextureCoords(box, txc); + drawAutoLightedCuboid(box, tiles, 6, txc, mask); } } diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index a4ed6a0fc..9d51ba2bc 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -86,7 +86,7 @@ private: template void drawCuboid(const aabb3f &box, const TileSpec *tiles, int tilecount, const f32 *txc, u8 mask, Fn &&face_lighter); - void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); + static void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); void drawAutoLightedCuboid(aabb3f box, const TileSpec &tile, f32 const *txc = nullptr, u8 mask = 0); void drawAutoLightedCuboid(aabb3f box, const TileSpec *tiles, int tile_count, f32 const *txc = nullptr, u8 mask = 0); u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const; diff --git a/src/client/meshgen/collector.cpp b/src/client/meshgen/collector.cpp index c8b726cde..6e3002624 100644 --- a/src/client/meshgen/collector.cpp +++ b/src/client/meshgen/collector.cpp @@ -14,25 +14,19 @@ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertice const TileLayer *layer = &tile.layers[layernum]; if (layer->empty()) continue; - append(*layer, vertices, numVertices, indices, numIndices, layernum, - tile.world_aligned); + append(*layer, vertices, numVertices, indices, numIndices, layernum); } } void MeshCollector::append(const TileLayer &layer, const video::S3DVertex *vertices, - u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum, - bool use_scale) + u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum) { PreMeshBuffer &p = findBuffer(layer, layernum, numVertices); - f32 scale = 1.0f; - if (use_scale) - scale = 1.0f / layer.scale; - u32 vertex_count = p.vertices.size(); for (u32 i = 0; i < numVertices; i++) { p.vertices.emplace_back(vertices[i].Pos + offset, vertices[i].Normal, - vertices[i].Color, scale * vertices[i].TCoords); + vertices[i].Color, vertices[i].TCoords); m_bounding_radius_sq = std::max(m_bounding_radius_sq, (vertices[i].Pos - m_center_pos).getLengthSQ()); } diff --git a/src/client/meshgen/collector.h b/src/client/meshgen/collector.h index f1f8a1481..876338baa 100644 --- a/src/client/meshgen/collector.h +++ b/src/client/meshgen/collector.h @@ -55,7 +55,7 @@ private: void append(const TileLayer &material, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices, - u8 layernum, bool use_scale = false); + u8 layernum); PreMeshBuffer &findBuffer(const TileLayer &layer, u8 layernum, u32 numVertices); }; From 452160cd000ab77dd35c80ae185966cf7dc28757 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 13 May 2025 17:43:30 +0200 Subject: [PATCH 21/48] Clean up read_tiledef and related parts a bit --- src/client/tile.h | 2 - src/script/common/c_content.cpp | 85 ++++++++++++++++++++------------- src/script/cpp_api/s_node.h | 1 - 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/client/tile.h b/src/client/tile.h index ffbe78bac..3293b4dd1 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -191,8 +191,6 @@ struct TileSpec bool world_aligned = false; //! Tile rotation. TileRotation rotation = TileRotation::None; - //! This much light does the tile emit. - u8 emissive_light = 0; //! The first is base texture, the second is overlay. TileLayer layers[MAX_TILE_LAYERS]; }; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index e4d94b3fc..91aa5b405 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -56,9 +56,16 @@ void read_item_definition(lua_State* L, int index, if (index < 0) index = lua_gettop(L) + 1 + index; - def.type = (ItemType)getenumfield(L, index, "type", - es_ItemType, ITEM_NONE); + def.name.clear(); getstringfield(L, index, "name", def.name); + + { + auto str = getstringfield_default(L, index, "type", ""); + if (!string_to_enum(es_ItemType, def.type, str)) + warningstream << "Item " << def.name + << " has unknown type \"" << str << '"' << std::endl; + } + getstringfield(L, index, "description", def.description); getstringfield(L, index, "short_description", def.short_description); getstringfield(L, index, "inventory_image", def.inventory_image); @@ -605,9 +612,6 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) case NDT_PLANTLIKE: case NDT_FIRELIKE: default_tiling = false; - // "break" is omitted here intentionaly, as PLANTLIKE - // FIRELIKE drawtype both should default to having - // backface_culling to false. [[fallthrough]]; case NDT_MESH: case NDT_LIQUID: @@ -621,7 +625,6 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) break; } - // key at index -2 and value at index if(lua_isstring(L, index)){ // "default_lava.png" tiledef.name = lua_tostring(L, index); @@ -634,7 +637,10 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) // name="default_lava.png" tiledef.name.clear(); getstringfield(L, index, "name", tiledef.name); - getstringfield(L, index, "image", tiledef.name); // MaterialSpec compat. + warn_if_field_exists(L, index, "image", "TileDef", + "Deprecated: new name is \"name\"."); + getstringfield(L, index, "image", tiledef.name); + tiledef.backface_culling = getboolfield_default( L, index, "backface_culling", default_culling); tiledef.tileable_horizontal = getboolfield_default( @@ -659,6 +665,9 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) lua_getfield(L, index, "animation"); tiledef.animation = read_animation_definition(L, -1); lua_pop(L, 1); + } else if (!lua_isnil(L, index)) { + // TODO: should be an error + errorstream << "TileDef: Invalid type! (expected string or table)" << std::endl; } return tiledef; @@ -672,13 +681,13 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) /* Cache existence of some callbacks */ lua_getfield(L, index, "on_construct"); - if(!lua_isnil(L, -1)) f.has_on_construct = true; + f.has_on_construct = !lua_isnil(L, -1); lua_pop(L, 1); lua_getfield(L, index, "on_destruct"); - if(!lua_isnil(L, -1)) f.has_on_destruct = true; + f.has_on_destruct = !lua_isnil(L, -1); lua_pop(L, 1); lua_getfield(L, index, "after_destruct"); - if(!lua_isnil(L, -1)) f.has_after_destruct = true; + f.has_after_destruct = !lua_isnil(L, -1); lua_pop(L, 1); lua_getfield(L, index, "on_rightclick"); @@ -695,8 +704,13 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) /* Visual definition */ - f.drawtype = (NodeDrawType)getenumfield(L, index, "drawtype", - ScriptApiNode::es_DrawType,NDT_NORMAL); + { + auto str = getstringfield_default(L, index, "drawtype", ""); + if (!string_to_enum(ScriptApiNode::es_DrawType, f.drawtype, str)) + warningstream << "Node " << f.name + << " has unknown drawtype \"" << str << '"' << std::endl; + } + getfloatfield(L, index, "visual_scale", f.visual_scale); /* Meshnode model filename */ @@ -796,10 +810,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) if (lua_toboolean(L, -1)) f.alpha = (f.drawtype == NDT_NORMAL) ? ALPHAMODE_CLIP : ALPHAMODE_BLEND; } else if (check_field_or_nil(L, -1, LUA_TSTRING, "use_texture_alpha")) { - int result = f.alpha; - string_to_enum(ScriptApiNode::es_TextureAlphaMode, result, - std::string(lua_tostring(L, -1))); - f.alpha = static_cast(result); + string_to_enum(ScriptApiNode::es_TextureAlphaMode, f.alpha, lua_tostring(L, -1)); } lua_pop(L, 1); @@ -817,10 +828,18 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) getboolfield(L, index, "post_effect_color_shaded", f.post_effect_color_shaded); - f.param_type = (ContentParamType)getenumfield(L, index, "paramtype", - ScriptApiNode::es_ContentParamType, CPT_NONE); - f.param_type_2 = (ContentParamType2)getenumfield(L, index, "paramtype2", - ScriptApiNode::es_ContentParamType2, CPT2_NONE); + { + auto str = getstringfield_default(L, index, "paramtype", ""); + if (!string_to_enum(ScriptApiNode::es_ContentParamType, f.param_type, str)) + warningstream << "Node " << f.name + << " has unknown paramtype \"" << str << '"' << std::endl; + } + { + auto str = getstringfield_default(L, index, "paramtype2", ""); + if (!string_to_enum(ScriptApiNode::es_ContentParamType2, f.param_type_2, str)) + warningstream << "Node " << f.name + << " has unknown paramtype2 \"" << str << '"' << std::endl; + } if (!f.palette_name.empty() && !(f.param_type_2 == CPT2_COLOR || @@ -855,8 +874,12 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) // Liquids flow into and replace node getboolfield(L, index, "floodable", f.floodable); // Whether the node is non-liquid, source liquid or flowing liquid - f.liquid_type = (LiquidType)getenumfield(L, index, "liquidtype", - ScriptApiNode::es_LiquidType, LIQUID_NONE); + { + auto str = getstringfield_default(L, index, "liquidtype", ""); + if (!string_to_enum(ScriptApiNode::es_LiquidType, f.liquid_type, str)) + warningstream << "Node " << f.name + << " has unknown liquidtype \"" << str << '"' << std::endl; + } // If the content is liquid, this is the flowing version of the liquid. getstringfield(L, index, "liquid_alternative_flowing", f.liquid_alternative_flowing); @@ -915,7 +938,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) lua_pushnil(L); while (lua_next(L, table) != 0) { // Value at -1 - std::string side(lua_tostring(L, -1)); + std::string_view side(lua_tostring(L, -1)); // Note faces are flipped to make checking easier if (side == "top") f.connect_sides |= 2; @@ -986,6 +1009,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) } else if(lua_isnil(L, -1)) { f.liquid_move_physics = f.liquid_type != LIQUID_NONE; } else { + // TODO: should be an error errorstream << "Field \"liquid_move_physics\": Invalid type!" << std::endl; } lua_pop(L, 1); @@ -1805,10 +1829,8 @@ WearBarParams read_wear_bar_params( auto blend = WearBarParams::BLEND_MODE_CONSTANT; lua_getfield(L, stack_idx, "blend"); if (check_field_or_nil(L, -1, LUA_TSTRING, "blend")) { - int blendInt; - if (!string_to_enum(WearBarParams::es_BlendMode, blendInt, std::string(lua_tostring(L, -1)))) + if (!string_to_enum(WearBarParams::es_BlendMode, blend, lua_tostring(L, -1))) throw LuaError("Invalid wear bar color blend mode"); - blend = static_cast(blendInt); } lua_pop(L, 1); @@ -2395,14 +2417,9 @@ void push_hud_element(lua_State *L, HudElement *elem) bool read_hud_change(lua_State *L, HudElementStat &stat, HudElement *elem, void **value) { std::string statstr = lua_tostring(L, 3); - { - int statint; - if (!string_to_enum(es_HudElementStat, statint, statstr)) { - script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream); - return false; - } - - stat = static_cast(statint); + if (!string_to_enum(es_HudElementStat, stat, statstr)) { + script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream); + return false; } switch (stat) { diff --git a/src/script/cpp_api/s_node.h b/src/script/cpp_api/s_node.h index bf4dc6af5..2c7133df1 100644 --- a/src/script/cpp_api/s_node.h +++ b/src/script/cpp_api/s_node.h @@ -38,7 +38,6 @@ public: static struct EnumString es_ContentParamType[]; static struct EnumString es_ContentParamType2[]; static struct EnumString es_LiquidType[]; - static struct EnumString es_LiquidMoveType[]; static struct EnumString es_NodeBoxType[]; static struct EnumString es_TextureAlphaMode[]; }; From 1214a1d4a6d3023e45feb5be034a83ca30b3c732 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 24 May 2025 22:49:29 +0200 Subject: [PATCH 22/48] Refactor ITextureSource use in main menu (#16135) --- irr/include/IVideoDriver.h | 4 ++-- src/client/clientlauncher.cpp | 19 +++++++++++++++---- src/client/shadows/dynamicshadowsrender.cpp | 2 +- src/gui/guiEngine.cpp | 19 +++++-------------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index f392eb636..02c9a2d4f 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -172,7 +172,7 @@ public: \return Pointer to the texture, or 0 if the texture could not be loaded. This pointer should not be dropped. See IReferenceCounted::drop() for more information. */ - virtual ITexture *getTexture(const io::path &filename) = 0; + [[deprecated]] virtual ITexture *getTexture(const io::path &filename) = 0; //! Get access to a named texture. /** Loads the texture from disk if it is not @@ -184,7 +184,7 @@ public: \return Pointer to the texture, or 0 if the texture could not be loaded. This pointer should not be dropped. See IReferenceCounted::drop() for more information. */ - virtual ITexture *getTexture(io::IReadFile *file) = 0; + [[deprecated]] virtual ITexture *getTexture(io::IReadFile *file) = 0; //! Returns amount of textures currently loaded /** \return Amount of textures currently loaded */ diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index a770dbcae..64f2e8f51 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -326,6 +326,18 @@ void ClientLauncher::setting_changed_callback(const std::string &name, void *dat static_cast(data)->config_guienv(); } +static video::ITexture *loadTexture(video::IVideoDriver *driver, const char *path) +{ + // FIXME?: it would be cleaner to do this through a ITextureSource, but we don't have one + video::ITexture *texture = nullptr; + verbosestream << "Loading texture " << path << std::endl; + if (auto *image = driver->createImageFromFile(path); image) { + texture = driver->addTexture(fs::GetFilenameFromPath(path), image); + image->drop(); + } + return texture; +} + void ClientLauncher::config_guienv() { gui::IGUISkin *skin = guienv->getSkin(); @@ -364,10 +376,9 @@ void ClientLauncher::config_guienv() if (cached_id != sprite_ids.end()) { skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, cached_id->second); } else { - gui::IGUISpriteBank *sprites = skin->getSpriteBank(); - video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); - video::ITexture *texture = driver->getTexture(path.c_str()); - s32 id = sprites->addTextureAsSprite(texture); + auto *driver = m_rendering_engine->get_video_driver(); + auto *texture = loadTexture(driver, path.c_str()); + s32 id = skin->getSpriteBank()->addTextureAsSprite(texture); if (id != -1) { skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, id); sprite_ids.emplace(path, id); diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 17260e21d..dbabf6dd7 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -410,7 +410,7 @@ video::ITexture *ShadowRenderer::getSMTexture(const std::string &shadow_map_name shadow_map_name.c_str(), texture_format); } - return m_driver->getTexture(shadow_map_name.c_str()); + return m_driver->findTexture(shadow_map_name.c_str()); } void ShadowRenderer::renderShadowMap(video::ITexture *target, diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 8cc9954fc..aef76aec7 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -74,6 +74,7 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id) if (retval) return retval; + verbosestream << "MenuTextureSource: loading " << name << std::endl; video::IImage *image = m_driver->createImageFromFile(name.c_str()); if (!image) return NULL; @@ -597,26 +598,16 @@ void GUIEngine::drawFooter(video::IVideoDriver *driver) bool GUIEngine::setTexture(texture_layer layer, const std::string &texturepath, bool tile_image, unsigned int minsize) { - video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); + m_textures[layer].texture = nullptr; - if (m_textures[layer].texture) { - driver->removeTexture(m_textures[layer].texture); - m_textures[layer].texture = NULL; - } - - if (texturepath.empty() || !fs::PathExists(texturepath)) { + if (texturepath.empty() || !fs::PathExists(texturepath)) return false; - } - m_textures[layer].texture = driver->getTexture(texturepath.c_str()); + m_textures[layer].texture = m_texture_source->getTexture(texturepath); m_textures[layer].tile = tile_image; m_textures[layer].minsize = minsize; - if (!m_textures[layer].texture) { - return false; - } - - return true; + return m_textures[layer].texture != nullptr; } /******************************************************************************/ From e9b32843a5989026b6ee49b525c965577d0ab17d Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 22 May 2025 17:02:22 -0500 Subject: [PATCH 23/48] Make MTP server shutdown flag atomic I noticed this potential data race while reading the code. I have not detected it with TSan in practice. --- src/network/mtp/impl.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/mtp/impl.h b/src/network/mtp/impl.h index 3f81fe27b..b8059f202 100644 --- a/src/network/mtp/impl.h +++ b/src/network/mtp/impl.h @@ -12,6 +12,7 @@ #include "util/numeric.h" #include "porting.h" #include "network/networkprotocol.h" +#include #include #include #include @@ -301,7 +302,7 @@ private: // Backwards compatibility PeerHandler *m_bc_peerhandler; - bool m_shutting_down = false; + std::atomic m_shutting_down = false; }; } // namespace From fa0c09d202537acbd267a38958f16c883359baca Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 22 May 2025 17:03:11 -0500 Subject: [PATCH 24/48] Do not modify peer timeout on shutdown Shortening the peer timeout was supposedly necessary at some point to work around an unknown bug. I was not able to reproduce the bug running a headless Luanti server on WSL Tumbleweed and connecting with a client on the Windows host. That is not enough to say the issue no longer exists. This commit may cause a regression. The access to change the peer timeout was unsynchronized and done by a different thread than the sending thread, so it was detected by TSan to be a data race. Since this patch deletes the code performing the write, the data race is no longer a concern and no synchronization must be added. --- src/network/mtp/impl.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/network/mtp/impl.cpp b/src/network/mtp/impl.cpp index 07bdfc735..d54c116a2 100644 --- a/src/network/mtp/impl.cpp +++ b/src/network/mtp/impl.cpp @@ -1314,11 +1314,6 @@ Connection::~Connection() 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(); From da7897a822e37a7b373e49f135318369c5c3ba17 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 May 2025 13:56:18 +0200 Subject: [PATCH 25/48] Fix texture double-free in main menu bug introduced in 1214a1d4a6d3023e45feb5be034a83ca30b3c732 --- src/gui/guiEngine.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index aef76aec7..b31f12ef9 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -405,12 +405,6 @@ GUIEngine::~GUIEngine() m_sound_manager.reset(); m_irr_toplefttext->remove(); - - // delete textures - for (image_definition &texture : m_textures) { - if (texture.texture) - m_rendering_engine->get_video_driver()->removeTexture(texture.texture); - } } /******************************************************************************/ From 94a9b94baf5c6626b1f917ef2007e760786401e8 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 27 May 2025 18:47:41 +0200 Subject: [PATCH 26/48] Formspec: Fix incorrect cell size when using non-default fonts (#16178) --- src/gui/guiFormSpecMenu.cpp | 10 +++++----- src/gui/guiTable.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 4b9601430..f1e91bd0b 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -1235,10 +1235,14 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); } - //now really show table GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, rect, m_tsrc); + // Apply styling before calculating the cell sizes + auto style = getDefaultStyleForElement("table", name); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1252,10 +1256,6 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) if (!str_initial_selection.empty() && str_initial_selection != "0") e->setSelected(stoi(str_initial_selection)); - auto style = getDefaultStyleForElement("table", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideFont(style.getFont()); - m_tables.emplace_back(spec, e); m_fields.push_back(spec); } diff --git a/src/gui/guiTable.h b/src/gui/guiTable.h index 7dbf17a51..2ade0ee3e 100644 --- a/src/gui/guiTable.h +++ b/src/gui/guiTable.h @@ -85,7 +85,7 @@ public: void setTextList(const std::vector &content, bool transparent); - /* Set generic table options, columns and content */ + /** Set generic table options, columns and content, calculate cell sizes */ // Adds empty strings to end of content if there is an incomplete row void setTable(const TableOptions &options, const TableColumns &columns, From 986cd32f289b4d315dcb645180db8abfc0d2ae86 Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 28 May 2025 07:29:03 -0400 Subject: [PATCH 27/48] Minor lua_api.md improvements (#16169) --- doc/client_lua_api.md | 5 +- doc/lua_api.md | 174 ++++++++++++++++++++++-------------------- 2 files changed, 96 insertions(+), 83 deletions(-) diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index 017f8b89b..ff4af5ccc 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -5,8 +5,11 @@ Luanti Lua Client Modding API Reference 5.13.0 it's now called `core` due to the renaming of Luanti (formerly Minetest). `minetest` will keep existing as an alias, so that old code won't break. +Note that `core` has already existed since version 0.4.10, so you can use it +safely without breaking backwards compatibility. + * More information at -* Developer Wiki: +* Additional documentation: Introduction ------------ diff --git a/doc/lua_api.md b/doc/lua_api.md index b604b317c..747c523a4 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5,8 +5,11 @@ Luanti Lua Modding API Reference it's now called `core` due to the renaming of Luanti (formerly Minetest). `minetest` will keep existing as an alias, so that old code won't break. +Note that `core` has already existed since version 0.4.10, so you can use it +safely without breaking backwards compatibility. + * More information at -* Developer Wiki: +* Additional documentation: * (Unofficial) Minetest Modding Book by rubenwardy: * Modding tools: @@ -4581,11 +4584,13 @@ and offset the noise variation. The final fractal value noise variation is created as follows: +``` noise = offset + scale * (octave1 + octave2 * persistence + octave3 * persistence ^ 2 + octave4 * persistence ^ 3 + ...) +``` Noise Parameters ---------------- @@ -4699,11 +4704,13 @@ with restraint. The absolute value of each octave's noise variation is used when combining the octaves. The final value noise variation is created as follows: +``` noise = offset + scale * (abs(octave1) + abs(octave2) * persistence + abs(octave3) * persistence ^ 2 + abs(octave4) * persistence ^ 3 + ...) +``` ### Format example @@ -6562,13 +6569,10 @@ Environment access * The actual seed used is the noiseparams seed plus the world seed. * `core.get_value_noise(seeddiff, octaves, persistence, spread)` * Deprecated: use `core.get_value_noise(noiseparams)` instead. - * Return world-specific value noise * `core.get_perlin(noiseparams)` - * Deprecated: use `core.get_value_noise(noiseparams)` instead. - * Return world-specific value noise (was not Perlin noise) + * Deprecated: renamed to `core.get_value_noise` in version 5.12.0. * `core.get_perlin(seeddiff, octaves, persistence, spread)` - * Deprecated: use `core.get_value_noise(noiseparams)` instead. - * Return world-specific value noise (was not Perlin noise) + * Deprecated: renamed to `core.get_value_noise` in version 5.12.0. * `core.get_voxel_manip([pos1, pos2])` * Return voxel manipulator object. * Loads the manipulator from the map if positions are passed. @@ -9061,78 +9065,6 @@ offering very strong randomness. * `get_state()`: return generator state encoded in string * `set_state(state_string)`: restore generator state from encoded string -`ValueNoise` -------------- - -A value noise generator. -It can be created via `ValueNoise()` or `core.get_value_noise()`. -For legacy reasons, it can also be created via `PerlinNoise()` or `core.get_perlin()`, -but the implemented noise is not Perlin noise. -For `core.get_value_noise()`, the actual seed used is the noiseparams seed -plus the world seed, to create world-specific noise. - -* `ValueNoise(noiseparams) -* `ValueNoise(seed, octaves, persistence, spread)` (Deprecated) -* `PerlinNoise(noiseparams)` (Deprecated) -* `PerlinNoise(seed, octaves, persistence, spread)` (Deprecated) - -* `core.get_value_noise(noiseparams)` -* `core.get_value_noise(seeddiff, octaves, persistence, spread)` (Deprecated) -* `core.get_perlin(noiseparams)` (Deprecated) -* `core.get_perlin(seeddiff, octaves, persistence, spread)` (Deprecated) - -### Methods - -* `get_2d(pos)`: returns 2D noise value at `pos={x=,y=}` -* `get_3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}` - -`ValueNoiseMap` ----------------- - -A fast, bulk noise generator. - -It can be created via `ValueNoiseMap(noiseparams, size)` or -`core.get_value_noise_map(noiseparams, size)`. -For legacy reasons, it can also be created via `PerlinNoiseMap(noiseparams, size)` -or `core.get_perlin_map(noiseparams, size)`, but it is not Perlin noise. -For `core.get_value_noise_map()`, the actual seed used is the noiseparams seed -plus the world seed, to create world-specific noise. - -Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted -for 2D noise, and it must be larger than 1 for 3D noise (otherwise -`nil` is returned). - -For each of the functions with an optional `buffer` parameter: If `buffer` is -not nil, this table will be used to store the result instead of creating a new -table. - -### Methods - -* `get_2d_map(pos)`: returns a `` times `` 2D array of 2D noise - with values starting at `pos={x=,y=}` -* `get_3d_map(pos)`: returns a `` times `` times `` - 3D array of 3D noise with values starting at `pos={x=,y=,z=}`. -* `get_2d_map_flat(pos, buffer)`: returns a flat `` element - array of 2D noise with values starting at `pos={x=,y=}` -* `get_3d_map_flat(pos, buffer)`: Same as `get2dMap_flat`, but 3D noise -* `calc_2d_map(pos)`: Calculates the 2d noise map starting at `pos`. The result - is stored internally. -* `calc_3d_map(pos)`: Calculates the 3d noise map starting at `pos`. The result - is stored internally. -* `get_map_slice(slice_offset, slice_size, buffer)`: In the form of an array, - returns a slice of the most recently computed noise results. The result slice - begins at coordinates `slice_offset` and takes a chunk of `slice_size`. - E.g., to grab a 2-slice high horizontal 2d plane of noise starting at buffer - offset y = 20: - `noisevals = noise:get_map_slice({y=20}, {y=2})` - It is important to note that `slice_offset` offset coordinates begin at 1, - and are relative to the starting position of the most recently calculated - noise. - To grab a single vertical column of noise starting at map coordinates - x = 1023, y=1000, z = 1000: - `noise:calc_3d_map({x=1000, y=1000, z=1000})` - `noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1})` - `PlayerMetaRef` --------------- @@ -9184,14 +9116,17 @@ end The map is loaded as the ray advances. If the map is modified after the `Raycast` is created, the changes may or may not have an effect on the object. -It can be created via `Raycast(pos1, pos2, objects, liquids)` or -`core.raycast(pos1, pos2, objects, liquids)` where: +It can be created via `Raycast(pos1, pos2, objects, liquids, pointabilities)` +or `core.raycast(pos1, pos2, objects, liquids, pointabilities)` where: * `pos1`: start of the ray * `pos2`: end of the ray -* `objects`: if false, only nodes will be returned. Default is true. +* `objects`: if false, only nodes will be returned. Default is `true`. * `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be - returned. Default is false. + returned. Default is `false`. +* `pointabilities`: Allows overriding the `pointable` property of + nodes and objects. Uses the same format as the `pointabilities` property + of item definitions. Default is `nil`. ### Limitations @@ -9307,6 +9242,81 @@ to restrictions of JSON. * All methods in MetaDataRef +`ValueNoise` +------------- + +A value noise generator. +It can be created via `ValueNoise()` or `core.get_value_noise()`. +For `core.get_value_noise()`, the actual seed used is the noiseparams seed +plus the world seed, to create world-specific noise. + +* `ValueNoise(noiseparams)` +* `ValueNoise(seed, octaves, persistence, spread)` (deprecated) +* `core.get_value_noise(noiseparams)` +* `core.get_value_noise(seeddiff, octaves, persistence, spread)` (deprecated) + +These were previously called `PerlinNoise()` and `core.get_perlin()`, but the +implemented noise was not Perlin noise. They were renamed in 5.12.0. The old +names still exist as aliases. + +### Methods + +* `get_2d(pos)`: returns 2D noise value at `pos={x=,y=}` +* `get_3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}` + +`ValueNoiseMap` +---------------- + +A fast, bulk noise generator. + +It can be created via `ValueNoiseMap(noiseparams, size)` or +`core.get_value_noise_map(noiseparams, size)`. +For `core.get_value_noise_map()`, the actual seed used is the noiseparams seed +plus the world seed, to create world-specific noise. + +These were previously called `PerlinNoiseMap()` and `core.get_perlin_map()`, +but the implemented noise was not Perlin noise. They were renamed in 5.12.0. +The old names still exist as aliases. + +Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted +for 2D noise, and it must be larger than 1 for 3D noise (otherwise +`nil` is returned). + +For each of the functions with an optional `buffer` parameter: If `buffer` is +not nil, this table will be used to store the result instead of creating a new +table. + +### Methods + +* `get_2d_map(pos)`: returns a `` times `` 2D array of 2D noise + with values starting at `pos={x=,y=}` +* `get_3d_map(pos)`: returns a `` times `` times `` + 3D array of 3D noise with values starting at `pos={x=,y=,z=}`. +* `get_2d_map_flat(pos, buffer)`: returns a flat `` element + array of 2D noise with values starting at `pos={x=,y=}` +* `get_3d_map_flat(pos, buffer)`: Same as `get2dMap_flat`, but 3D noise +* `calc_2d_map(pos)`: Calculates the 2d noise map starting at `pos`. The result + is stored internally. +* `calc_3d_map(pos)`: Calculates the 3d noise map starting at `pos`. The result + is stored internally. +* `get_map_slice(slice_offset, slice_size, buffer)`: In the form of an array, + returns a slice of the most recently computed noise results. The result slice + begins at coordinates `slice_offset` and takes a chunk of `slice_size`. + E.g., to grab a 2-slice high horizontal 2d plane of noise starting at buffer + offset `y = 20`: + ```lua + noisevals = noise:get_map_slice({y=20}, {y=2}) + ``` + It is important to note that `slice_offset` offset coordinates begin at 1, + and are relative to the starting position of the most recently calculated + noise. + To grab a single vertical column of noise starting at map coordinates + `x = 1023, y=1000, z = 1000`: + ```lua + noise:calc_3d_map({x=1000, y=1000, z=1000}) + noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1}) + ``` + From ae35f37bc30ccddb0b9ae69341e5d7b4b876099a Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 28 May 2025 13:29:20 +0200 Subject: [PATCH 28/48] Move one CI run to be on 64-bit ARM --- .github/workflows/linux.yml | 14 +++++++------- util/ci/common.sh | 13 ++++++++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e5c98a901..14d63f4fb 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -132,16 +132,16 @@ jobs: run: | ./util/test_multiplayer.sh - # Build with prometheus-cpp (server-only) - clang_11_prometheus: - name: "clang_11 (PROMETHEUS=1)" - runs-on: ubuntu-22.04 + # Build with prometheus-cpp (server-only), also runs on ARM64 + clang_prometheus_arm: + name: "clang (with Prometheus, ARM64)" + runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh - install_linux_deps clang-11 + install_linux_deps --headless clang libluajit-5.1-dev - name: Build prometheus-cpp run: ./util/ci/build_prometheus_cpp.sh @@ -150,8 +150,8 @@ jobs: run: | ./util/ci/build.sh env: - CC: clang-11 - CXX: clang++-11 + CC: clang + CXX: clang++ CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0 -DENABLE_CURSES=0" - name: Test diff --git a/util/ci/common.sh b/util/ci/common.sh index 374aabf5f..4ee68c8b9 100644 --- a/util/ci/common.sh +++ b/util/ci/common.sh @@ -2,12 +2,19 @@ # Linux build only install_linux_deps() { + local graphics=1 + if [[ "$1" == "--headless" ]]; then + graphics= + shift + fi local pkgs=( cmake gettext postgresql + libsqlite3-dev libhiredis-dev libogg-dev libgmp-dev libpq-dev + libleveldb-dev libcurl4-openssl-dev libzstd-dev libssl-dev + ) + [ -n "$graphics" ] && pkgs+=( libpng-dev libjpeg-dev libgl1-mesa-dev libsdl2-dev libfreetype-dev - libsqlite3-dev libhiredis-dev libogg-dev libgmp-dev libvorbis-dev - libopenal-dev libpq-dev libleveldb-dev libcurl4-openssl-dev libzstd-dev - libssl-dev + libogg-dev libvorbis-dev libopenal-dev ) sudo apt-get update From a5263dc7ed4108b7ea57c08f82555bc7b2b4053c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 18 May 2025 22:44:20 +0200 Subject: [PATCH 29/48] Do not allow vector components to be nil --- doc/breakages.md | 1 + src/script/common/c_converter.cpp | 69 ++++++++++++++----------------- src/script/common/c_converter.h | 13 +++++- src/script/lua_api/l_env.cpp | 23 +++++++---- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/doc/breakages.md b/doc/breakages.md index 412cf2e41..6c9acbd95 100644 --- a/doc/breakages.md +++ b/doc/breakages.md @@ -25,3 +25,4 @@ This list is largely advisory and items may be reevaluated once the time comes. * remove built-in knockback and related functions entirely * remove `safe` parameter from `core.serialize`, always enforce `safe = true`. possibly error when `loadstring` calls are encountered in `core.deserialize`. +* introduce strict type checking for all instances of `v3s16` / `v3f` read from Lua diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 138063d54..5b7331e47 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -18,6 +18,8 @@ extern "C" { #include #include "common/c_types.h" +static v3d read_v3d(lua_State *L, int index); +static v3d check_v3d(lua_State *L, int index); #define CHECK_TYPE(index, name, type) do { \ int t = lua_type(L, (index)); \ @@ -28,6 +30,13 @@ extern "C" { } \ } while(0) +#define CHECK_NOT_NIL(index, name) do { \ + if (lua_isnoneornil(L, (index))) { \ + throw LuaError(std::string("Invalid ") + (name) + \ + " (value is nil)."); \ + } \ + } while(0) + #define CHECK_FLOAT(value, name) do {\ if (std::isnan(value) || std::isinf(value)) { \ throw LuaError("Invalid float value for '" name \ @@ -35,7 +44,13 @@ extern "C" { } \ } while (0) +// strictly check type of coordinate +// (this won't permit string-to-int conversion, so maybe not the best idea?) #define CHECK_POS_COORD(index, name) CHECK_TYPE(index, "vector coordinate " name, LUA_TNUMBER) +// loosely check type of coordinate +#define CHECK_POS_COORD2(index, name) CHECK_NOT_NIL(index, "vector coordinate " name) + +// Note: not needed when using read_v3_aux #define CHECK_POS_TAB(index) CHECK_TYPE(index, "vector", LUA_TTABLE) @@ -44,6 +59,7 @@ extern "C" { */ static void read_v3_aux(lua_State *L, int index) { + // TODO: someone find out if it's faster to have the type check in Lua too CHECK_POS_TAB(index); lua_pushvalue(L, index); lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_VECTOR); @@ -87,24 +103,12 @@ void push_v2f(lua_State *L, v2f p) v2s16 read_v2s16(lua_State *L, int index) { - v2s16 p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - p.X = lua_tonumber(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - p.Y = lua_tonumber(L, -1); - lua_pop(L, 1); - return p; + return v2s16::from(read_v2f(L, index)); } void push_v2s16(lua_State *L, v2s16 p) { - lua_createtable(L, 0, 2); - lua_pushinteger(L, p.X); - lua_setfield(L, -2, "x"); - lua_pushinteger(L, p.Y); - lua_setfield(L, -2, "y"); + push_v2s32(L, v2s32::from(p)); } void push_v2s32(lua_State *L, v2s32 p) @@ -127,15 +131,7 @@ void push_v2u32(lua_State *L, v2u32 p) v2s32 read_v2s32(lua_State *L, int index) { - v2s32 p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - p.X = lua_tonumber(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - p.Y = lua_tonumber(L, -1); - lua_pop(L, 1); - return p; + return v2s32::from(read_v2f(L, index)); } v2f read_v2f(lua_State *L, int index) @@ -143,9 +139,11 @@ v2f read_v2f(lua_State *L, int index) v2f p; CHECK_POS_TAB(index); lua_getfield(L, index, "x"); + CHECK_POS_COORD2(-1, "x"); p.X = lua_tonumber(L, -1); lua_pop(L, 1); lua_getfield(L, index, "y"); + CHECK_POS_COORD2(-1, "y"); p.Y = lua_tonumber(L, -1); lua_pop(L, 1); return p; @@ -170,30 +168,20 @@ v2f check_v2f(lua_State *L, int index) v3f read_v3f(lua_State *L, int index) { - read_v3_aux(L, index); - float x = lua_tonumber(L, -3); - float y = lua_tonumber(L, -2); - float z = lua_tonumber(L, -1); - lua_pop(L, 3); - return v3f(x, y, z); + return v3f::from(read_v3d(L, index)); } v3f check_v3f(lua_State *L, int index) { - read_v3_aux(L, index); - CHECK_POS_COORD(-3, "x"); - CHECK_POS_COORD(-2, "y"); - CHECK_POS_COORD(-1, "z"); - float x = lua_tonumber(L, -3); - float y = lua_tonumber(L, -2); - float z = lua_tonumber(L, -1); - lua_pop(L, 3); - return v3f(x, y, z); + return v3f::from(check_v3d(L, index)); } v3d read_v3d(lua_State *L, int index) { read_v3_aux(L, index); + CHECK_POS_COORD2(-3, "x"); + CHECK_POS_COORD2(-2, "y"); + CHECK_POS_COORD2(-1, "z"); double x = lua_tonumber(L, -3); double y = lua_tonumber(L, -2); double z = lua_tonumber(L, -1); @@ -286,18 +274,23 @@ video::SColor read_ARGB8(lua_State *L, int index) return std::fmax(0.0, std::fmin(255.0, c)); }; + // FIXME: maybe we should have strict type checks here. compare to is_color_table() + video::SColor color(0); CHECK_TYPE(index, "ARGB color", LUA_TTABLE); lua_getfield(L, index, "a"); color.setAlpha(lua_isnumber(L, -1) ? clamp_col(lua_tonumber(L, -1)) : 0xFF); lua_pop(L, 1); lua_getfield(L, index, "r"); + CHECK_NOT_NIL(-1, "color component R"); color.setRed(clamp_col(lua_tonumber(L, -1))); lua_pop(L, 1); lua_getfield(L, index, "g"); + CHECK_NOT_NIL(-1, "color component G"); color.setGreen(clamp_col(lua_tonumber(L, -1))); lua_pop(L, 1); lua_getfield(L, index, "b"); + CHECK_NOT_NIL(-1, "color component B"); color.setBlue(clamp_col(lua_tonumber(L, -1))); lua_pop(L, 1); return color; diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index b55f1c9c9..2744fa0b5 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -74,13 +74,23 @@ v2f check_v2f(lua_State *L, int index); v3f check_v3f(lua_State *L, int index); v3s16 check_v3s16(lua_State *L, int index); +// TODO: some day we should figure out the type-checking situation so it's done +// everywhere. (right now {x=true, y=false} as v2f is {0,0} with no warning) + +/// @warning relaxed type-checking, prefer `check_v3f`. v3f read_v3f(lua_State *L, int index); +/// @warning relaxed type-checking, prefer `check_v2f`. v2f read_v2f(lua_State *L, int index); +/// @warning relaxed type-checking v2s16 read_v2s16(lua_State *L, int index); +/// @warning relaxed type-checking v2s32 read_v2s32(lua_State *L, int index); +/// @warning relaxed type-checking, prefer `check_v3s16`. +v3s16 read_v3s16(lua_State *L, int index); + video::SColor read_ARGB8(lua_State *L, int index); bool read_color(lua_State *L, int index, video::SColor *color); -bool is_color_table (lua_State *L, int index); +bool is_color_table(lua_State *L, int index); /** * Read a floating-point axis-aligned box from Lua. @@ -95,7 +105,6 @@ bool is_color_table (lua_State *L, int index); */ aabb3f read_aabb3f(lua_State *L, int index, f32 scale); -v3s16 read_v3s16(lua_State *L, int index); std::vector read_aabb3f_vector (lua_State *L, int index, f32 scale); size_t read_stringlist(lua_State *L, int index, std::vector *result); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index aafde7540..423479040 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -266,15 +266,22 @@ int ModApiEnv::l_bulk_swap_node(lua_State *L) // get_node_raw(x, y, z) -> content, param1, param2, pos_ok int ModApiEnv::l_get_node_raw(lua_State *L) { - GET_ENV_PTR; + GET_PLAIN_ENV_PTR; - // pos - // mirrors implementation of read_v3s16 (with the exact same rounding) - double x = lua_tonumber(L, 1); - double y = lua_tonumber(L, 2); - double z = lua_tonumber(L, 3); - v3s16 pos = doubleToInt(v3d(x, y, z), 1.0); - // Do it + v3s16 pos; + // mirrors the implementation of read_v3s16 (with the exact same rounding) + { + if (lua_isnoneornil(L, 1)) + throw LuaError("X position is nil"); + if (lua_isnoneornil(L, 2)) + throw LuaError("Y position is nil"); + if (lua_isnoneornil(L, 3)) + throw LuaError("Z position is nil"); + double x = lua_tonumber(L, 1); + double y = lua_tonumber(L, 2); + double z = lua_tonumber(L, 3); + pos = doubleToInt(v3d(x, y, z), 1.0); + } bool pos_ok; MapNode n = env->getMap().getNode(pos, &pos_ok); // Return node and pos_ok From ec16fb33d0fb224220d323120986711b99d2e19e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 19 May 2025 17:41:13 +0200 Subject: [PATCH 30/48] Add unit tests for Lua vector reading --- builtin/common/strict.lua | 17 ++- games/devtest/mods/unittests/misc.lua | 12 -- src/unittest/CMakeLists.txt | 1 + src/unittest/test.h | 4 +- src/unittest/test_scriptapi.cpp | 183 ++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 20 deletions(-) create mode 100644 src/unittest/test_scriptapi.cpp diff --git a/builtin/common/strict.lua b/builtin/common/strict.lua index 9bfa8d7a2..b3c4ccce4 100644 --- a/builtin/common/strict.lua +++ b/builtin/common/strict.lua @@ -19,12 +19,14 @@ function meta:__newindex(name, value) return end local info = getinfo(2, "Sl") - local desc = ("%s:%d"):format(info.short_src, info.currentline) - local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name) - if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then - core.log("warning", ("Assignment to undeclared global %q inside a function at %s.") - :format(name, desc)) - warned[warn_key] = true + if info ~= nil then + local desc = ("%s:%d"):format(info.short_src, info.currentline) + local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name) + if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then + core.log("warning", ("Assignment to undeclared global %q inside a function at %s.") + :format(name, desc)) + warned[warn_key] = true + end end declared[name] = true end @@ -35,6 +37,9 @@ function meta:__index(name) return end local info = getinfo(2, "Sl") + if info == nil then + return + end local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name) if not warned[warn_key] and info.what ~= "C" then core.log("warning", ("Undeclared global variable %q accessed at %s:%s") diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index 65dc3259e..28cc2c1eb 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -67,18 +67,6 @@ local function test_dynamic_media(cb, player) end unittests.register("test_dynamic_media", test_dynamic_media, {async=true, player=true}) -local function test_v3f_metatable(player) - assert(vector.check(player:get_pos())) -end -unittests.register("test_v3f_metatable", test_v3f_metatable, {player=true}) - -local function test_v3s16_metatable(player, pos) - local node = core.get_node(pos) - local found_pos = core.find_node_near(pos, 0, node.name, true) - assert(vector.check(found_pos)) -end -unittests.register("test_v3s16_metatable", test_v3s16_metatable, {map=true}) - local function test_clear_meta(_, pos) local ref = core.get_meta(pos) diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 9ac275d7f..2a38af68d 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -36,6 +36,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_sao.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_scriptapi.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_serialization.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_serveractiveobjectmgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_server_shutdown_state.cpp diff --git a/src/unittest/test.h b/src/unittest/test.h index dcecb9fb4..7e861a24a 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -41,7 +41,7 @@ public: #define UTEST(x, fmt, ...) \ if (!(x)) { \ char utest_buf[1024]; \ - snprintf(utest_buf, sizeof(utest_buf), fmt, __VA_ARGS__); \ + porting::mt_snprintf(utest_buf, sizeof(utest_buf), fmt, __VA_ARGS__); \ throw TestFailedException(utest_buf, __FILE__, __LINE__); \ } @@ -68,7 +68,7 @@ public: } catch (EType &e) { \ exception_thrown = true; \ } \ - UASSERT(exception_thrown); \ + UTEST(exception_thrown, "Exception %s not thrown", #EType); \ } class IGameDef; diff --git a/src/unittest/test_scriptapi.cpp b/src/unittest/test_scriptapi.cpp new file mode 100644 index 000000000..4fc03e6c5 --- /dev/null +++ b/src/unittest/test_scriptapi.cpp @@ -0,0 +1,183 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2022 Minetest core developers & community + +#include "test.h" + +#include +#include "script/cpp_api/s_base.h" +#include "script/lua_api/l_util.h" +#include "script/lua_api/l_settings.h" +#include "script/common/c_converter.h" +#include "irrlicht_changes/printing.h" +#include "server.h" + +namespace { + class MyScriptApi : virtual public ScriptApiBase { + public: + MyScriptApi() : ScriptApiBase(ScriptingType::Async) {}; + void init(); + using ScriptApiBase::getStack; + }; +} + +class TestScriptApi : public TestBase +{ +public: + TestScriptApi() { TestManager::registerTestModule(this); } + const char *getName() { return "TestScriptApi"; } + + void runTests(IGameDef *gamedef); + + void testVectorMetatable(MyScriptApi *script); + void testVectorRead(MyScriptApi *script); + void testVectorReadErr(MyScriptApi *script); + void testVectorReadMix(MyScriptApi *script); +}; + +static TestScriptApi g_test_instance; + +void MyScriptApi::init() +{ + lua_State *L = getStack(); + + lua_getglobal(L, "core"); + int top = lua_gettop(L); + + // By creating an environment of 'async' type we have the fewest amount + // of external classes needed. + lua_pushstring(L, "async"); + lua_setglobal(L, "INIT"); + + LuaSettings::Register(L); + ModApiUtil::InitializeAsync(L, top); + + lua_pop(L, 1); + + loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua", BUILTIN_MOD_NAME); + checkSetByBuiltin(); +} + +void TestScriptApi::runTests(IGameDef *gamedef) +{ + MyScriptApi script; + try { + script.init(); + } catch (ModError &e) { + rawstream << e.what() << std::endl; + num_tests_failed = 1; + return; + } + + TEST(testVectorMetatable, &script); + TEST(testVectorRead, &script); + TEST(testVectorReadErr, &script); + TEST(testVectorReadMix, &script); +} + +// Runs Lua code and leaves `nresults` return values on the stack +static void run(lua_State *L, const char *code, int nresults) +{ + if (luaL_loadstring(L, code) != 0) { + rawstream << lua_tostring(L, -1) << std::endl; + UASSERT(false); + } + if (lua_pcall(L, 0, nresults, 0) != 0) { + throw LuaError(lua_tostring(L, -1)); + } +} + +void TestScriptApi::testVectorMetatable(MyScriptApi *script) +{ + lua_State *L = script->getStack(); + StackUnroller unroller(L); + + const auto &call_vector_check = [&] () -> bool { + lua_setglobal(L, "tmp"); + run(L, "return vector.check(tmp)", 1); + return lua_toboolean(L, -1); + }; + + push_v3s16(L, {1, 2, 3}); + UASSERT(call_vector_check()); + + push_v3f(L, {1, 2, 3}); + UASSERT(call_vector_check()); + + // 2-component vectors must not have this metatable + push_v2s32(L, {0, 0}); + UASSERT(!call_vector_check()); + + push_v2f(L, {0, 0}); + UASSERT(!call_vector_check()); +} + +void TestScriptApi::testVectorRead(MyScriptApi *script) +{ + lua_State *L = script->getStack(); + StackUnroller unroller(L); + + // both methods should parse these + const std::pair pairs1[] = { + {"return {x=1, y=-2, z=3}", {1, -2, 3}}, + {"return {x=1.1, y=0, z=0}", {1, 0, 0}}, + {"return {x=1.5, y=0, z=0}", {2, 0, 0}}, + {"return {x=-1.1, y=0, z=0}", {-1, 0, 0}}, + {"return {x=-1.5, y=0, z=0}", {-2, 0, 0}}, + {"return vector.new(5, 6, 7)", {5, 6, 7}}, + {"return vector.new(32767, 0, -32768)", {S16_MAX, 0, S16_MIN}}, + }; + for (auto &it : pairs1) { + run(L, it.first, 1); + v3s16 v = read_v3s16(L, -1); + UASSERTEQ(auto, v, it.second); + v = check_v3s16(L, -1); + UASSERTEQ(auto, v, it.second); + lua_pop(L, 1); + } +} + +void TestScriptApi::testVectorReadErr(MyScriptApi *script) +{ + lua_State *L = script->getStack(); + StackUnroller unroller(L); + + // both methods should reject these + const char *errs1[] = { + "return {y=1, z=3}", + "return {x=1, z=3}", + "return {x=1, y=3}", + "return {}", + "return 'bamboo'", + "return function() end", + "return nil", + }; + for (auto &it : errs1) { + infostream << it << std::endl; + run(L, it, 1); + EXCEPTION_CHECK(LuaError, read_v3s16(L, -1)); + EXCEPTION_CHECK(LuaError, check_v3s16(L, -1)); + } +} + +void TestScriptApi::testVectorReadMix(MyScriptApi *script) +{ + lua_State *L = script->getStack(); + StackUnroller unroller(L); + + // read_v3s16 should allow these, but check_v3s16 should not + const std::pair pairs2[] = { + {"return {x='3', y='2.9', z=3}", {3, 3, 3}}, + {"return {x=false, y=0, z=0}", {0, 0, 0}}, + {"return {x='?', y=0, z=0}", {0, 0, 0}}, + {"return {x={'baguette'}, y=0, z=0}", {0, 0, 0}}, + }; + for (auto &it : pairs2) { + infostream << it.first << std::endl; + run(L, it.first, 1); + v3s16 v = read_v3s16(L, -1); + UASSERTEQ(auto, v, it.second); + EXCEPTION_CHECK(LuaError, check_v3s16(L, -1)); + lua_pop(L, 1); + } +} From 6ca9d75f0bacfe71a232be4f590cc4f5d15ed068 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 19 May 2025 17:44:59 +0200 Subject: [PATCH 31/48] Reject NaN and Inf in check_v3d() too check_v2f() was already doing this --- src/script/common/c_converter.cpp | 3 +++ src/unittest/test_scriptapi.cpp | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 5b7331e47..a8c9ace3a 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -199,6 +199,9 @@ v3d check_v3d(lua_State *L, int index) double y = lua_tonumber(L, -2); double z = lua_tonumber(L, -1); lua_pop(L, 3); + CHECK_FLOAT(x, "x"); + CHECK_FLOAT(y, "y"); + CHECK_FLOAT(z, "z"); return v3d(x, y, z); } diff --git a/src/unittest/test_scriptapi.cpp b/src/unittest/test_scriptapi.cpp index 4fc03e6c5..0e407daf5 100644 --- a/src/unittest/test_scriptapi.cpp +++ b/src/unittest/test_scriptapi.cpp @@ -180,4 +180,17 @@ void TestScriptApi::testVectorReadMix(MyScriptApi *script) EXCEPTION_CHECK(LuaError, check_v3s16(L, -1)); lua_pop(L, 1); } + + // same but even the result is undefined + const char *errs2[] = { + "return {x=0, y=0, z=0/0}", // nan + "return {x=0, y=0, z=math.huge}", // inf + }; + for (auto &it : errs2) { + infostream << it << std::endl; + run(L, it, 1); + (void)read_v3s16(L, -1); + EXCEPTION_CHECK(LuaError, check_v3s16(L, -1)); + lua_pop(L, 1); + } } From 5c2599315cb6295196a5dc8f96debc930f28b8ad Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 26 May 2025 21:42:21 +0200 Subject: [PATCH 32/48] Change nil-component error to deprecation warning --- src/script/common/c_converter.cpp | 7 +++++-- src/script/lua_api/l_env.cpp | 6 +++--- src/unittest/test_scriptapi.cpp | 8 ++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index a8c9ace3a..2b711bfda 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -30,10 +30,13 @@ static v3d check_v3d(lua_State *L, int index); } \ } while(0) +// TODO: this should be turned into an error in 2026. +// Just revert the commit that added this line. #define CHECK_NOT_NIL(index, name) do { \ if (lua_isnoneornil(L, (index))) { \ - throw LuaError(std::string("Invalid ") + (name) + \ - " (value is nil)."); \ + auto msg = std::string("Invalid ") + (name) + \ + " (value is nil)."; \ + log_deprecated(L, msg, 1, true); \ } \ } while(0) diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 423479040..e21a954ac 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -272,11 +272,11 @@ int ModApiEnv::l_get_node_raw(lua_State *L) // mirrors the implementation of read_v3s16 (with the exact same rounding) { if (lua_isnoneornil(L, 1)) - throw LuaError("X position is nil"); + log_deprecated(L, "X position is nil", 1, true); if (lua_isnoneornil(L, 2)) - throw LuaError("Y position is nil"); + log_deprecated(L, "Y position is nil", 1, true); if (lua_isnoneornil(L, 3)) - throw LuaError("Z position is nil"); + log_deprecated(L, "Z position is nil", 1, true); double x = lua_tonumber(L, 1); double y = lua_tonumber(L, 2); double z = lua_tonumber(L, 3); diff --git a/src/unittest/test_scriptapi.cpp b/src/unittest/test_scriptapi.cpp index 0e407daf5..03c3713b2 100644 --- a/src/unittest/test_scriptapi.cpp +++ b/src/unittest/test_scriptapi.cpp @@ -144,10 +144,6 @@ void TestScriptApi::testVectorReadErr(MyScriptApi *script) // both methods should reject these const char *errs1[] = { - "return {y=1, z=3}", - "return {x=1, z=3}", - "return {x=1, y=3}", - "return {}", "return 'bamboo'", "return function() end", "return nil", @@ -167,6 +163,10 @@ void TestScriptApi::testVectorReadMix(MyScriptApi *script) // read_v3s16 should allow these, but check_v3s16 should not const std::pair pairs2[] = { + {"return {}", {0, 0, 0}}, + {"return {y=1, z=3}", {0, 1, 3}}, + {"return {x=1, z=3}", {1, 0, 3}}, + {"return {x=1, y=3}", {1, 3, 0}}, {"return {x='3', y='2.9', z=3}", {3, 3, 3}}, {"return {x=false, y=0, z=0}", {0, 0, 0}}, {"return {x='?', y=0, z=0}", {0, 0, 0}}, From 957ebf73682b33793dcceed7028480e153ebedd8 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Fri, 30 May 2025 13:02:15 +0200 Subject: [PATCH 33/48] settingtypes: Add `no-c-format` flag for xgettext --- builtin/common/settings/generate_from_settingtypes.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/common/settings/generate_from_settingtypes.lua b/builtin/common/settings/generate_from_settingtypes.lua index 4c33a8fc1..13b3035ce 100644 --- a/builtin/common/settings/generate_from_settingtypes.lua +++ b/builtin/common/settings/generate_from_settingtypes.lua @@ -102,7 +102,7 @@ end local translation_file_header = [[ // This file is automatically generated // It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files -// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua +// To update it, refer to the bottom of builtin/common/settings/init.lua fake_function() {]] @@ -110,15 +110,15 @@ local function create_translation_file(settings) local result = { translation_file_header } for _, entry in ipairs(settings) do if entry.type == "category" then - insert(result, sprintf("\tgettext(%q);", entry.name)) + insert(result, sprintf("\t/* xgettext:no-c-format */ gettext(%q);", entry.name)) else if entry.readable_name then - insert(result, sprintf("\tgettext(%q);", entry.readable_name)) + insert(result, sprintf("\t/* xgettext:no-c-format */ gettext(%q);", entry.readable_name)) end if entry.comment ~= "" then local comment_escaped = entry.comment:gsub("\n", "\\n") comment_escaped = comment_escaped:gsub("\"", "\\\"") - insert(result, "\tgettext(\"" .. comment_escaped .. "\");") + insert(result, "\t/* xgettext:no-c-format */ gettext(\"" .. comment_escaped .. "\");") end end end From 9ce9d7f4334c9520154644a7be3af98ddc334c80 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 May 2025 11:31:16 +0200 Subject: [PATCH 34/48] Extend check for lingering clinets --- src/network/serverpackethandler.cpp | 1 + src/script/lua_api/l_server.cpp | 2 +- src/server/clientiface.cpp | 13 +++++-------- src/server/clientiface.h | 7 +++++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index d9a38e878..e16494652 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -302,6 +302,7 @@ void Server::handleCommand_Init2(NetworkPacket* pkt) sendMediaAnnouncement(peer_id, lang); RemoteClient *client = getClient(peer_id, CS_InitDone); + assert(client); // Keep client language for server translations client->setLangCode(lang); diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 74ee5e5c9..698b2dba6 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -261,7 +261,7 @@ int ModApiServer::l_get_player_information(lua_State *L) lua_settable(L, table); lua_pushstring(L,"state"); - lua_pushstring(L, ClientInterface::state2Name(info.state).c_str()); + lua_pushstring(L, ClientInterface::state2Name(info.state)); lua_settable(L, table); #endif diff --git a/src/server/clientiface.cpp b/src/server/clientiface.cpp index 13cf14c07..6b5f66456 100644 --- a/src/server/clientiface.cpp +++ b/src/server/clientiface.cpp @@ -47,7 +47,7 @@ const char *ClientInterface::statenames[] = { "SudoMode", }; -std::string ClientInterface::state2Name(ClientState state) +const char *ClientInterface::state2Name(ClientState state) { return statenames[state]; } @@ -703,16 +703,13 @@ void ClientInterface::step(float dtime) RecursiveMutexAutoLock clientslock(m_clients_mutex); for (const auto &it : m_clients) { auto state = it.second->getState(); - if (state >= CS_HelloSent) + if (state >= CS_InitDone) continue; if (it.second->uptime() <= LINGER_TIMEOUT) continue; - // CS_Created means nobody has even noticed the client is there - // (this is before on_prejoinplayer runs) - // CS_Invalid should not happen - // -> log those as warning, the rest as info - std::ostream &os = state == CS_Created || state == CS_Invalid ? - warningstream : infostream; + // Complain louder if this situation is unexpected + auto &os = state == CS_Disconnecting || state == CS_Denied ? + infostream : warningstream; try { Address addr = m_con->GetPeerAddress(it.second->peer_id); os << "Disconnecting lingering client from " diff --git a/src/server/clientiface.h b/src/server/clientiface.h index 44b278e63..30b0454ef 100644 --- a/src/server/clientiface.h +++ b/src/server/clientiface.h @@ -495,7 +495,8 @@ public: m_env = env; } - static std::string state2Name(ClientState state); + static const char *state2Name(ClientState state); + protected: class AutoLock { public: @@ -526,5 +527,7 @@ private: static const char *statenames[]; - static constexpr int LINGER_TIMEOUT = 10; + // Note that this puts a fixed timeout on the init & auth phase for a client. + // (lingering is enforced until CS_InitDone) + static constexpr int LINGER_TIMEOUT = 12; }; From b580c66b6143d22a7437c930327917dd0ced8d26 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 May 2025 11:55:18 +0200 Subject: [PATCH 35/48] Restrict minimum state for ClientInterface::sendToAll --- src/server.cpp | 7 +++---- src/server/clientiface.cpp | 32 +++++++++++--------------------- src/server/clientiface.h | 2 +- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index 008b91022..efe74119c 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1572,7 +1572,8 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message) if (peer_id != PEER_ID_INEXISTENT) { Send(&pkt); } else { - m_clients.sendToAll(&pkt); + // If a client has completed auth but is still joining, still send chat + m_clients.sendToAll(&pkt, CS_InitDone); } } @@ -3183,9 +3184,7 @@ std::wstring Server::handleChat(const std::string &name, ChatMessage chatmsg(line); - std::vector clients = m_clients.getClientIDs(); - for (u16 cid : clients) - SendChatMessage(cid, chatmsg); + SendChatMessage(PEER_ID_INEXISTENT, chatmsg); return L""; } diff --git a/src/server/clientiface.cpp b/src/server/clientiface.cpp index 6b5f66456..2f6abbf85 100644 --- a/src/server/clientiface.cpp +++ b/src/server/clientiface.cpp @@ -767,33 +767,22 @@ void ClientInterface::sendCustom(session_t peer_id, u8 channel, NetworkPacket *p m_con->Send(peer_id, channel, pkt, reliable); } -void ClientInterface::sendToAll(NetworkPacket *pkt) +void ClientInterface::sendToAll(NetworkPacket *pkt, ClientState state_min) { + auto &ccf = clientCommandFactoryTable[pkt->getCommand()]; + FATAL_ERROR_IF(!ccf.name, "packet type missing in table"); RecursiveMutexAutoLock clientslock(m_clients_mutex); - for (auto &client_it : m_clients) { - RemoteClient *client = client_it.second; - - if (client->net_proto_version != 0) { - auto &ccf = clientCommandFactoryTable[pkt->getCommand()]; - FATAL_ERROR_IF(!ccf.name, "packet type missing in table"); - m_con->Send(client->peer_id, ccf.channel, pkt, ccf.reliable); - } + for (auto &[peer_id, client] : m_clients) { + if (client->getState() >= state_min) + m_con->Send(peer_id, ccf.channel, pkt, ccf.reliable); } } RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min) { RecursiveMutexAutoLock clientslock(m_clients_mutex); - RemoteClientMap::const_iterator n = m_clients.find(peer_id); - // The client may not exist; clients are immediately removed if their - // access is denied, and this event occurs later then. - if (n == m_clients.end()) - return NULL; - - if (n->second->getState() >= state_min) - return n->second; - - return NULL; + RemoteClient *client = lockedGetClientNoEx(peer_id, state_min); + return client; } RemoteClient* ClientInterface::lockedGetClientNoEx(session_t peer_id, ClientState state_min) @@ -802,12 +791,13 @@ RemoteClient* ClientInterface::lockedGetClientNoEx(session_t peer_id, ClientStat // The client may not exist; clients are immediately removed if their // access is denied, and this event occurs later then. if (n == m_clients.end()) - return NULL; + return nullptr; + assert(n->second->peer_id == peer_id); if (n->second->getState() >= state_min) return n->second; - return NULL; + return nullptr; } ClientState ClientInterface::getClientState(session_t peer_id) diff --git a/src/server/clientiface.h b/src/server/clientiface.h index 30b0454ef..d2194774c 100644 --- a/src/server/clientiface.h +++ b/src/server/clientiface.h @@ -458,7 +458,7 @@ public: void sendCustom(session_t peer_id, u8 channel, NetworkPacket *pkt, bool reliable); /* send to all clients */ - void sendToAll(NetworkPacket *pkt); + void sendToAll(NetworkPacket *pkt, ClientState state_min = CS_Active); /* delete a client */ void DeleteClient(session_t peer_id); From 79e0d834fdfd024c134a92c78c7d3e0ee3dfefc1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 May 2025 12:52:59 +0200 Subject: [PATCH 36/48] Rework user limit and checks around join process --- src/network/serverpackethandler.cpp | 88 +++++++++++++++-------------- src/server.cpp | 10 +++- src/server.h | 5 ++ src/server/clientiface.cpp | 35 +----------- src/server/clientiface.h | 13 +---- 5 files changed, 67 insertions(+), 84 deletions(-) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index e16494652..e9012d351 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -40,10 +40,6 @@ void Server::handleCommand_Deprecated(NetworkPacket* pkt) void Server::handleCommand_Init(NetworkPacket* pkt) { - - if(pkt->getSize() < 1) - return; - session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Created); @@ -75,15 +71,6 @@ void Server::handleCommand_Init(NetworkPacket* pkt) verbosestream << "Server: Got TOSERVER_INIT from " << addr_s << " (peer_id=" << peer_id << ")" << std::endl; - // Do not allow multiple players in simple singleplayer mode. - // This isn't a perfect way to do it, but will suffice for now - if (m_simple_singleplayer_mode && !m_clients.getClientIDs().empty()) { - infostream << "Server: Not allowing another client (" << addr_s << - ") to connect in simple singleplayer mode" << std::endl; - DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER); - return; - } - if (denyIfBanned(peer_id)) return; @@ -161,18 +148,14 @@ void Server::handleCommand_Init(NetworkPacket* pkt) return; } - RemotePlayer *player = m_env->getPlayer(playername, true); - - // If player is already connected, cancel - if (player && player->getPeerId() != PEER_ID_INEXISTENT) { - actionstream << "Server: Player with name \"" << playername << - "\" tried to connect, but player with same name is already connected" << std::endl; - DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED); + // Do not allow multiple players in simple singleplayer mode + if (isSingleplayer() && !m_clients.getClientIDs(CS_HelloSent).empty()) { + infostream << "Server: Not allowing another client (" << addr_s << + ") to connect in simple singleplayer mode" << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER); return; } - - m_clients.setPlayerName(peer_id, playername); - + // Or the "singleplayer" name to be used on regular servers if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) { actionstream << "Server: Player with the name \"singleplayer\" tried " "to connect from " << addr_s << std::endl; @@ -180,12 +163,25 @@ void Server::handleCommand_Init(NetworkPacket* pkt) return; } + { + RemotePlayer *player = m_env->getPlayer(playername, true); + // If player is already connected, cancel + if (player && player->getPeerId() != PEER_ID_INEXISTENT) { + actionstream << "Server: Player with name \"" << playername << + "\" tried to connect, but player with same name is already connected" << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED); + return; + } + } + + client->setName(playerName); + { std::string reason; if (m_script->on_prejoinplayer(playername, addr_s, &reason)) { actionstream << "Server: Player with the name \"" << playerName << "\" tried to connect from " << addr_s << - " but it was disallowed for the following reason: " << reason << + " but was disallowed for the following reason: " << reason << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, reason); return; @@ -195,14 +191,11 @@ void Server::handleCommand_Init(NetworkPacket* pkt) infostream << "Server: New connection: \"" << playerName << "\" from " << addr_s << " (peer_id=" << peer_id << ")" << std::endl; - // Enforce user limit. - // Don't enforce for users that have some admin right or mod permits it. - if (m_clients.isUserLimitReached() && - playername != g_settings->get("name") && - !m_script->can_bypass_userlimit(playername, addr_s)) { + // Early check for user limit, so the client doesn't need to run + // through the join process only to be denied. + if (checkUserLimit(playerName, addr_s)) { actionstream << "Server: " << playername << " tried to join from " << - addr_s << ", but there are already max_users=" << - g_settings->getU16("max_users") << " players." << std::endl; + addr_s << ", but the user limit was reached." << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS); return; } @@ -355,6 +348,8 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt) void Server::handleCommand_ClientReady(NetworkPacket* pkt) { session_t peer_id = pkt->getPeerId(); + RemoteClient *client = getClient(peer_id, CS_Created); + assert(client); // decode all information first u8 major_ver, minor_ver, patch_ver, reserved; @@ -365,8 +360,17 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt) if (pkt->getRemainingBytes() >= 2) *pkt >> formspec_ver; - m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver, - full_ver); + client->setVersionInfo(major_ver, minor_ver, patch_ver, full_ver); + + // Since only active clients count for the user limit, two could race the + // join process so we have to do a final check for the user limit here. + std::string addr_s = client->getAddress().serializeString(); + if (checkUserLimit(client->getName(), addr_s)) { + actionstream << "Server: " << client->getName() << " tried to join from " << + addr_s << ", but the user limit was reached (late)." << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS); + return; + } // Emerge player PlayerSAO* playersao = StageTwoClientInit(peer_id); @@ -1427,7 +1431,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) std::string salt, verification_key; - std::string addr_s = getPeerAddress(peer_id).serializeString(); + std::string addr_s = client->getAddress().serializeString(); u8 is_empty; *pkt >> salt >> verification_key >> is_empty; @@ -1513,9 +1517,11 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); + std::string addr_s = client->getAddress().serializeString(); + if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { actionstream << "Server: got SRP _A packet in wrong state " << cstate << - " from " << getPeerAddress(peer_id).serializeString() << + " from " << addr_s << ". Ignoring." << std::endl; return; } @@ -1525,7 +1531,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) if (client->chosen_mech != AUTH_MECHANISM_NONE) { actionstream << "Server: got SRP _A packet, while auth is already " "going on with mech " << client->chosen_mech << " from " << - getPeerAddress(peer_id).serializeString() << + addr_s << " (wantSudo=" << wantSudo << "). Ignoring." << std::endl; if (wantSudo) { DenySudoAccess(peer_id); @@ -1542,7 +1548,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) infostream << "Server: TOSERVER_SRP_BYTES_A received with " << "based_on=" << int(based_on) << " and len_A=" - << bytes_A.length() << "." << std::endl; + << bytes_A.length() << std::endl; AuthMechanism chosen = (based_on == 0) ? AUTH_MECHANISM_LEGACY_PASSWORD : AUTH_MECHANISM_SRP; @@ -1551,17 +1557,17 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) // Right now, the auth mechs don't change between login and sudo mode. if (!client->isMechAllowed(chosen)) { actionstream << "Server: Player \"" << client->getName() << - "\" at " << getPeerAddress(peer_id).serializeString() << + "\" from " << addr_s << " tried to change password using unallowed mech " << chosen << - "." << std::endl; + std::endl; DenySudoAccess(peer_id); return; } } else { if (!client->isMechAllowed(chosen)) { actionstream << "Server: Client tried to authenticate from " << - getPeerAddress(peer_id).serializeString() << - " using unallowed mech " << chosen << "." << std::endl; + addr_s << + " using unallowed mech " << chosen << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); return; } diff --git a/src/server.cpp b/src/server.cpp index efe74119c..a1263ef1d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -3356,6 +3356,15 @@ bool Server::denyIfBanned(session_t peer_id) return false; } +bool Server::checkUserLimit(const std::string &player_name, const std::string &addr_s) +{ + if (!m_clients.isUserLimitReached()) + return false; + if (player_name == g_settings->get("name")) // admin can always join + return false; + return !m_script->can_bypass_userlimit(player_name, addr_s); +} + void Server::notifyPlayer(const char *name, const std::wstring &msg) { // m_env will be NULL if the server is initializing @@ -3489,7 +3498,6 @@ void Server::hudSetHotbarSelectedImage(RemotePlayer *player, const std::string & Address Server::getPeerAddress(session_t peer_id) { - // Note that this is only set after Init was received in Server::handleCommand_Init return getClient(peer_id, CS_Invalid)->getAddress(); } diff --git a/src/server.h b/src/server.h index c9869e1dd..ae5b10682 100644 --- a/src/server.h +++ b/src/server.h @@ -368,6 +368,7 @@ public: void hudSetHotbarImage(RemotePlayer *player, const std::string &name); void hudSetHotbarSelectedImage(RemotePlayer *player, const std::string &name); + /// @note this is only available for client state >= CS_HelloSent Address getPeerAddress(session_t peer_id); void setLocalPlayerAnimations(RemotePlayer *player, v2f animation_frames[4], @@ -611,6 +612,10 @@ private: void handleChatInterfaceEvent(ChatEvent *evt); + /// @brief Checks if user limit allows a potential client to join + /// @return true if the client can NOT join + bool checkUserLimit(const std::string &player_name, const std::string &addr_s); + // This returns the answer to the sender of wmessage, or "" if there is none std::wstring handleChat(const std::string &name, std::wstring wmessage_input, bool check_shout_priv = false, RemotePlayer *player = nullptr); diff --git a/src/server/clientiface.cpp b/src/server/clientiface.cpp index 2f6abbf85..bbcd4720d 100644 --- a/src/server/clientiface.cpp +++ b/src/server/clientiface.cpp @@ -659,7 +659,7 @@ std::vector ClientInterface::getClientIDs(ClientState min_state) { std::vector reply; RecursiveMutexAutoLock clientslock(m_clients_mutex); - + reply.reserve(m_clients.size()); for (const auto &m_client : m_clients) { if (m_client.second->getState() >= min_state) reply.push_back(m_client.second->peer_id); @@ -677,14 +677,10 @@ void ClientInterface::markBlocksNotSent(const std::vector &positions, boo } } -/** - * Verify if user limit was reached. - * User limit count all clients from HelloSent state (MT protocol user) to Active state - * @return true if user limit was reached - */ bool ClientInterface::isUserLimitReached() { - return getClientIDs(CS_HelloSent).size() >= g_settings->getU16("max_users"); + // Note that this only counts clients that have fully joined + return getClientIDs().size() >= g_settings->getU16("max_users"); } void ClientInterface::step(float dtime) @@ -812,16 +808,6 @@ ClientState ClientInterface::getClientState(session_t peer_id) return n->second->getState(); } -void ClientInterface::setPlayerName(session_t peer_id, const std::string &name) -{ - RecursiveMutexAutoLock clientslock(m_clients_mutex); - RemoteClientMap::iterator n = m_clients.find(peer_id); - // The client may not exist; clients are immediately removed if their - // access is denied, and this event occurs later then. - if (n != m_clients.end()) - n->second->setName(name); -} - void ClientInterface::DeleteClient(session_t peer_id) { RecursiveMutexAutoLock conlock(m_clients_mutex); @@ -902,18 +888,3 @@ u16 ClientInterface::getProtocolVersion(session_t peer_id) return n->second->net_proto_version; } - -void ClientInterface::setClientVersion(session_t peer_id, u8 major, u8 minor, u8 patch, - const std::string &full) -{ - RecursiveMutexAutoLock conlock(m_clients_mutex); - - // Error check - RemoteClientMap::iterator n = m_clients.find(peer_id); - - // No client to set versions - if (n == m_clients.end()) - return; - - n->second->setVersionInfo(major, minor, patch, full); -} diff --git a/src/server/clientiface.h b/src/server/clientiface.h index d2194774c..d0e91dcca 100644 --- a/src/server/clientiface.h +++ b/src/server/clientiface.h @@ -445,7 +445,7 @@ public: /* mark blocks as not sent on all active clients */ void markBlocksNotSent(const std::vector &positions, bool low_priority = false); - /* verify is server user limit was reached */ + /* verify if server user limit was reached */ bool isUserLimitReached(); /* get list of client player names */ @@ -475,16 +475,9 @@ public: /* get state of client by id*/ ClientState getClientState(session_t peer_id); - /* set client playername */ - void setPlayerName(session_t peer_id, const std::string &name); - /* get protocol version of client */ u16 getProtocolVersion(session_t peer_id); - /* set client version */ - void setClientVersion(session_t peer_id, u8 major, u8 minor, u8 patch, - const std::string &full); - /* event to update client state */ void event(session_t peer_id, ClientStateEvent event); @@ -515,9 +508,9 @@ private: // Connection std::shared_ptr m_con; std::recursive_mutex m_clients_mutex; - // Connected clients (behind the con mutex) + // Connected clients (behind the mutex) RemoteClientMap m_clients; - std::vector m_clients_names; //for announcing masterserver + std::vector m_clients_names; // for announcing to server list // Environment ServerEnvironment *m_env; From e03957ec0c72d8ed94883579e3a0dd7a3ba301d2 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 25 May 2025 12:39:11 +0200 Subject: [PATCH 37/48] Add VoxelManip:close() for explicit free --- doc/lua_api.md | 7 +++++++ src/script/lua_api/l_vmanip.cpp | 14 ++++++++++++++ src/script/lua_api/l_vmanip.h | 2 ++ 3 files changed, 23 insertions(+) diff --git a/doc/lua_api.md b/doc/lua_api.md index 747c523a4..cd7707343 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5249,6 +5249,13 @@ Methods 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. +* `close()`: Frees the data buffers associated with the VoxelManip object. + It will become empty. + * Since Lua's garbage collector is not aware of the potentially significant + memory behind a VoxelManip, frequent VoxelManip usage can cause the server to + run out of RAM. Therefore it's recommend to call this method once you're done + with the VoxelManip. + * (introduced in 5.13.0) `VoxelArea` ----------- diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index 5815d3bf3..09a9f9ffa 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -344,6 +344,19 @@ int LuaVoxelManip::l_get_emerged_area(lua_State *L) return 2; } +int LuaVoxelManip::l_close(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaVoxelManip *o = checkObject(L, 1); + + if (o->is_mapgen_vm) + throw LuaError("Cannot dispose of mapgen VoxelManip object"); + o->vm->clear(); + + return 0; +} + LuaVoxelManip::LuaVoxelManip(MMVManip *mmvm, bool is_mg_vm) : is_mapgen_vm(is_mg_vm), vm(mmvm) @@ -451,5 +464,6 @@ const luaL_Reg LuaVoxelManip::methods[] = { luamethod(LuaVoxelManip, set_param2_data), luamethod(LuaVoxelManip, was_modified), luamethod(LuaVoxelManip, get_emerged_area), + luamethod(LuaVoxelManip, close), {0,0} }; diff --git a/src/script/lua_api/l_vmanip.h b/src/script/lua_api/l_vmanip.h index 5ba1caffa..bfd83f56c 100644 --- a/src/script/lua_api/l_vmanip.h +++ b/src/script/lua_api/l_vmanip.h @@ -45,6 +45,8 @@ private: static int l_was_modified(lua_State *L); static int l_get_emerged_area(lua_State *L); + static int l_close(lua_State *L); + public: MMVManip *vm = nullptr; From 41651c73172cc90c9e1bccb5a705bf97954bbff1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 25 May 2025 13:31:16 +0200 Subject: [PATCH 38/48] Add C++-side unit tests for MMVManip --- src/dummymap.h | 7 +- src/map.cpp | 4 +- src/unittest/test_voxelmanipulator.cpp | 122 +++++++++++++++++-------- 3 files changed, 89 insertions(+), 44 deletions(-) diff --git a/src/dummymap.h b/src/dummymap.h index 2da371884..408f91341 100644 --- a/src/dummymap.h +++ b/src/dummymap.h @@ -27,12 +27,13 @@ public: void fill(v3s16 bpmin, v3s16 bpmax, MapNode n) { for (s16 z = bpmin.Z; z <= bpmax.Z; z++) - for (s16 y = bpmin.Y; y <= bpmax.Y; y++) - for (s16 x = bpmin.X; x <= bpmax.X; x++) { + for (s16 x = bpmin.X; x <= bpmax.X; x++) + for (s16 y = bpmin.Y; y <= bpmax.Y; y++) { MapBlock *block = getBlockNoCreateNoEx({x, y, z}); if (block) { + auto *data = block->getData(); for (size_t i = 0; i < MapBlock::nodecount; i++) - block->getData()[i] = n; + data[i] = n; block->expireIsAirCache(); } } diff --git a/src/map.cpp b/src/map.cpp index d88200846..b04376de3 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -772,7 +772,7 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent) infostream<emergeBlock(p, true); + assert(block); block->copyTo(*this); } else { flags |= VMANIP_BLOCK_DATA_INEXIST; diff --git a/src/unittest/test_voxelmanipulator.cpp b/src/unittest/test_voxelmanipulator.cpp index 09d4b5781..d677ac5e0 100644 --- a/src/unittest/test_voxelmanipulator.cpp +++ b/src/unittest/test_voxelmanipulator.cpp @@ -4,11 +4,13 @@ #include "test.h" -#include +#include #include "gamedef.h" #include "log.h" #include "voxel.h" +#include "dummymap.h" +#include "irrlicht_changes/printing.h" class TestVoxelManipulator : public TestBase { public: @@ -17,59 +19,30 @@ public: void runTests(IGameDef *gamedef); - void testVoxelArea(); - void testVoxelManipulator(const NodeDefManager *nodedef); + void testBasic(const NodeDefManager *nodedef); + void testEmerge(IGameDef *gamedef); + void testBlitBack(IGameDef *gamedef); }; static TestVoxelManipulator g_test_instance; void TestVoxelManipulator::runTests(IGameDef *gamedef) { - TEST(testVoxelArea); - TEST(testVoxelManipulator, gamedef->getNodeDefManager()); + TEST(testBasic, gamedef->ndef()); + TEST(testEmerge, gamedef); + TEST(testBlitBack, gamedef); } //////////////////////////////////////////////////////////////////////////////// -void TestVoxelManipulator::testVoxelArea() -{ - VoxelArea a(v3s16(-1,-1,-1), v3s16(1,1,1)); - UASSERT(a.index(0,0,0) == 1*3*3 + 1*3 + 1); - UASSERT(a.index(-1,-1,-1) == 0); - - VoxelArea c(v3s16(-2,-2,-2), v3s16(2,2,2)); - // An area that is 1 bigger in x+ and z- - VoxelArea d(v3s16(-2,-2,-3), v3s16(3,2,2)); - - std::list aa; - d.diff(c, aa); - - // Correct results - std::vector results; - results.emplace_back(v3s16(-2,-2,-3), v3s16(3,2,-3)); - results.emplace_back(v3s16(3,-2,-2), v3s16(3,2,2)); - - UASSERT(aa.size() == results.size()); - - infostream<<"Result of diff:"<print(infostream); - infostream << std::endl; - - auto j = std::find(results.begin(), results.end(), *it); - UASSERT(j != results.end()); - results.erase(j); - } -} - - -void TestVoxelManipulator::testVoxelManipulator(const NodeDefManager *nodedef) +void TestVoxelManipulator::testBasic(const NodeDefManager *nodedef) { VoxelManipulator v; v.print(infostream, nodedef); + UASSERT(v.m_area.hasEmptyExtent()); - infostream << "*** Setting (-1,0,-1)=2 ***" << std::endl; + infostream << "*** Setting (-1,0,-1) ***" << std::endl; v.setNode(v3s16(-1,0,-1), MapNode(t_CONTENT_GRASS)); v.print(infostream, nodedef); @@ -89,3 +62,74 @@ void TestVoxelManipulator::testVoxelManipulator(const NodeDefManager *nodedef) UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == t_CONTENT_GRASS); EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,1,1))); } + +void TestVoxelManipulator::testEmerge(IGameDef *gamedef) +{ + constexpr int bs = MAP_BLOCKSIZE; + + DummyMap map(gamedef, {0,0,0}, {1,1,1}); + map.fill({0,0,0}, {1,1,1}, CONTENT_AIR); + + MMVManip vm(&map); + UASSERT(!vm.isOrphan()); + + // emerge something + vm.initialEmerge({0,0,0}, {0,0,0}); + UASSERTEQ(auto, vm.m_area.MinEdge, v3s16(0)); + UASSERTEQ(auto, vm.m_area.MaxEdge, v3s16(bs-1)); + UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,0,0}).getContent(), CONTENT_AIR); + + map.setNode({0, 1,0}, t_CONTENT_BRICK); + map.setNode({0,bs+1,0}, t_CONTENT_BRICK); + + // emerge top block: this should not re-read the first one + vm.initialEmerge({0,0,0}, {0,1,0}); + UASSERTEQ(auto, vm.m_area.getExtent(), v3s32(bs,2*bs,bs)); + + UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0, 1,0}).getContent(), CONTENT_AIR); + UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,bs+1,0}).getContent(), t_CONTENT_BRICK); + + // emerge out of bounds: should produce empty data + vm.initialEmerge({0,0,0}, {0,2,0}, false); + UASSERTEQ(auto, vm.m_area.getExtent(), v3s32(bs,3*bs,bs)); + + UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,2*bs,0}).getContent(), CONTENT_IGNORE); + UASSERT(!vm.exists({0,2*bs,0})); + + // clear + vm.clear(); + UASSERT(vm.m_area.hasEmptyExtent()); +} + +void TestVoxelManipulator::testBlitBack(IGameDef *gamedef) +{ + DummyMap map(gamedef, {-1,-1,-1}, {1,1,1}); + map.fill({0,0,0}, {0,0,0}, CONTENT_AIR); + + std::unique_ptr vm2; + + { + MMVManip vm(&map); + vm.initialEmerge({0,0,0}, {0,0,0}); + UASSERT(vm.exists({0,0,0})); + vm.setNodeNoEmerge({0,0,0}, t_CONTENT_STONE); + vm.setNodeNoEmerge({1,1,1}, t_CONTENT_GRASS); + vm.setNodeNoEmerge({2,2,2}, CONTENT_IGNORE); + // test out clone and reparent too + vm2.reset(vm.clone()); + } + + UASSERT(vm2); + UASSERT(vm2->isOrphan()); + vm2->reparent(&map); + + std::map modified; + vm2->blitBackAll(&modified); + UASSERTEQ(size_t, modified.size(), 1); + UASSERTEQ(auto, modified.begin()->first, v3s16(0,0,0)); + + UASSERTEQ(auto, map.getNode({0,0,0}).getContent(), t_CONTENT_STONE); + UASSERTEQ(auto, map.getNode({1,1,1}).getContent(), t_CONTENT_GRASS); + // ignore nodes are not written (is this an intentional feature?) + UASSERTEQ(auto, map.getNode({2,2,2}).getContent(), CONTENT_AIR); +} From 6274a8dec49972ffb2a66d8254d1089b7a5fcc7e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 25 May 2025 14:50:13 +0200 Subject: [PATCH 39/48] Refactor MMVManip to get rid of m_loaded_blocks --- src/map.cpp | 71 ++++++++++++++++++-------- src/map.h | 36 +++++++------ src/unittest/test_voxelarea.cpp | 4 ++ src/unittest/test_voxelmanipulator.cpp | 48 ++++++++++++++++- src/voxel.h | 2 +- 5 files changed, 123 insertions(+), 38 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index b04376de3..1c1dfd51c 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -772,6 +772,11 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent) infostream< had_blocks; + // we can skip this calculation if the areas are disjoint + if (!m_area.intersect(block_area_nodes).hasEmptyExtent()) + had_blocks = getCoveredBlocks(); + const bool all_new = m_area.hasEmptyExtent(); addArea(block_area_nodes); @@ -779,12 +784,12 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent) for(s32 y=p_min.Y; y<=p_max.Y; y++) for(s32 x=p_min.X; x<=p_max.X; x++) { - u8 flags = 0; - MapBlock *block; v3s16 p(x,y,z); - if (m_loaded_blocks.count(p) > 0) + // if this block was already in the vmanip and it has data, skip + if (auto it = had_blocks.find(p); it != had_blocks.end() && it->second) continue; + MapBlock *block; bool block_data_inexistent = false; { TimeTaker timer2("emerge load", &emerge_load_time); @@ -803,21 +808,53 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent) assert(block); block->copyTo(*this); } else { - flags |= VMANIP_BLOCK_DATA_INEXIST; - // Mark area inexistent VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); setFlags(a, VOXELFLAG_NO_DATA); } } - - m_loaded_blocks[p] = flags; } if (all_new) m_is_dirty = false; } +std::map MMVManip::getCoveredBlocks() const +{ + std::map ret; + if (m_area.hasEmptyExtent()) + return ret; + + // Figure out if *any* node in this block has data according to m_flags + const auto &check_block = [this] (v3s16 bp) -> bool { + v3s16 pmin = bp * MAP_BLOCKSIZE; + v3s16 pmax = pmin + v3s16(MAP_BLOCKSIZE-1); + for(s16 z=pmin.Z; z<=pmax.Z; z++) + for(s16 y=pmin.Y; y<=pmax.Y; y++) + for(s16 x=pmin.X; x<=pmax.X; x++) { + if (!(m_flags[m_area.index(x,y,z)] & VOXELFLAG_NO_DATA)) + return true; + } + return false; + }; + + v3s16 bpmin = getNodeBlockPos(m_area.MinEdge); + v3s16 bpmax = getNodeBlockPos(m_area.MaxEdge); + + if (bpmin * MAP_BLOCKSIZE != m_area.MinEdge) + throw BaseException("MMVManip not block-aligned"); + if ((bpmax+1) * MAP_BLOCKSIZE - v3s16(1) != m_area.MaxEdge) + throw BaseException("MMVManip not block-aligned"); + + for(s16 z=bpmin.Z; z<=bpmax.Z; z++) + for(s16 y=bpmin.Y; y<=bpmax.Y; y++) + for(s16 x=bpmin.X; x<=bpmax.X; x++) { + v3s16 bp(x,y,z); + ret[bp] = check_block(bp); + } + return ret; +} + void MMVManip::blitBackAll(std::map *modified_blocks, bool overwrite_generated) const { @@ -825,16 +862,14 @@ void MMVManip::blitBackAll(std::map *modified_blocks, return; assert(m_map); - /* - Copy data of all blocks - */ - assert(!m_loaded_blocks.empty()); - for (auto &loaded_block : m_loaded_blocks) { - v3s16 p = loaded_block.first; + // Copy all the blocks with data back to the map + const auto loaded_blocks = getCoveredBlocks(); + for (auto &it : loaded_blocks) { + if (!it.second) + continue; + v3s16 p = it.first; MapBlock *block = m_map->getBlockNoCreateNoEx(p); - bool existed = !(loaded_block.second & VMANIP_BLOCK_DATA_INEXIST); - if (!existed || (block == NULL) || - (!overwrite_generated && block->isGenerated())) + if (!block || (!overwrite_generated && block->isGenerated())) continue; block->copyFrom(*this); @@ -860,11 +895,7 @@ MMVManip *MMVManip::clone() const ret->m_flags = new u8[size]; memcpy(ret->m_flags, m_flags, size * sizeof(u8)); } - ret->m_is_dirty = m_is_dirty; - // Even if the copy is disconnected from a map object keep the information - // needed to write it back to one - ret->m_loaded_blocks = m_loaded_blocks; return ret; } diff --git a/src/map.h b/src/map.h index 9ec66cfb9..57ede8ee6 100644 --- a/src/map.h +++ b/src/map.h @@ -307,16 +307,29 @@ public: MMVManip(Map *map); virtual ~MMVManip() = default; - virtual void clear() - { - VoxelManipulator::clear(); - m_loaded_blocks.clear(); - } - + /* + Loads specified area from map and *adds* it to the area already + contained in the VManip. + */ void initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, bool load_if_inexistent = true); - // This is much faster with big chunks of generated data + /** + Uses the flags array to determine which blocks the VManip covers, + and for which of them we have any data. + @warning requires VManip area to be block-aligned + @return map of blockpos -> any data? + */ + std::map getCoveredBlocks() const; + + /** + Writes data in VManip back to the map. Blocks without any data in the VManip + are skipped. + @note VOXELFLAG_NO_DATA is checked per-block, not per-node. So you need + to ensure that the relevant parts of m_data are initialized. + @param modified_blocks output array of touched blocks (optional) + @param overwrite_generated if false, blocks marked as generate in the map are not changed + */ void blitBackAll(std::map * modified_blocks, bool overwrite_generated = true) const; @@ -339,13 +352,4 @@ protected: // may be null Map *m_map = nullptr; - /* - key = blockpos - value = flags describing the block - */ - std::map m_loaded_blocks; - - enum : u8 { - VMANIP_BLOCK_DATA_INEXIST = 1 << 0, - }; }; diff --git a/src/unittest/test_voxelarea.cpp b/src/unittest/test_voxelarea.cpp index d9380caf9..52bf1bfd8 100644 --- a/src/unittest/test_voxelarea.cpp +++ b/src/unittest/test_voxelarea.cpp @@ -264,6 +264,10 @@ void TestVoxelArea::test_intersect() UASSERT(v3.intersect(v1) == v1.intersect(v3)); UASSERT(v1.intersect(v4) == VoxelArea({-10, -2, -10}, {10, 2, 10})); + + // edge cases + UASSERT(VoxelArea().intersect(v1).hasEmptyExtent()); + UASSERT(v1.intersect(VoxelArea()).hasEmptyExtent()); } void TestVoxelArea::test_index_xyz_all_pos() diff --git a/src/unittest/test_voxelmanipulator.cpp b/src/unittest/test_voxelmanipulator.cpp index d677ac5e0..9c1f85d24 100644 --- a/src/unittest/test_voxelmanipulator.cpp +++ b/src/unittest/test_voxelmanipulator.cpp @@ -22,6 +22,7 @@ public: void testBasic(const NodeDefManager *nodedef); void testEmerge(IGameDef *gamedef); void testBlitBack(IGameDef *gamedef); + void testBlitBack2(IGameDef *gamedef); }; static TestVoxelManipulator g_test_instance; @@ -31,6 +32,7 @@ void TestVoxelManipulator::runTests(IGameDef *gamedef) TEST(testBasic, gamedef->ndef()); TEST(testEmerge, gamedef); TEST(testBlitBack, gamedef); + TEST(testBlitBack2, gamedef); } //////////////////////////////////////////////////////////////////////////////// @@ -90,7 +92,7 @@ void TestVoxelManipulator::testEmerge(IGameDef *gamedef) UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,bs+1,0}).getContent(), t_CONTENT_BRICK); // emerge out of bounds: should produce empty data - vm.initialEmerge({0,0,0}, {0,2,0}, false); + vm.initialEmerge({0,2,0}, {0,2,0}, false); UASSERTEQ(auto, vm.m_area.getExtent(), v3s32(bs,3*bs,bs)); UASSERTEQ(auto, vm.getNodeNoExNoEmerge({0,2*bs,0}).getContent(), CONTENT_IGNORE); @@ -133,3 +135,47 @@ void TestVoxelManipulator::testBlitBack(IGameDef *gamedef) // ignore nodes are not written (is this an intentional feature?) UASSERTEQ(auto, map.getNode({2,2,2}).getContent(), CONTENT_AIR); } + +void TestVoxelManipulator::testBlitBack2(IGameDef *gamedef) +{ + constexpr int bs = MAP_BLOCKSIZE; + + DummyMap map(gamedef, {0,0,0}, {1,1,1}); + map.fill({0,0,0}, {1,1,1}, CONTENT_AIR); + + // Create a vmanip "manually" without using initialEmerge + MMVManip vm(&map); + vm.addArea(VoxelArea({0,0,0}, v3s16(1,2,1) * bs - v3s16(1))); + + // Lower block is initialized with ignore, upper with lava + for(s16 z=0; z= bs ? t_CONTENT_LAVA : CONTENT_IGNORE; + vm.setNodeNoEmerge({x,y,z}, c); + } + // But pretend the upper block was not actually initialized + vm.setFlags(VoxelArea({0,bs,0}, v3s16(1,2,1) * bs - v3s16(1)), VOXELFLAG_NO_DATA); + // Add a node to the lower one + vm.setNodeNoEmerge({0,1,0}, t_CONTENT_TORCH); + + // Verify covered blocks + { + auto cov = vm.getCoveredBlocks(); + UASSERTEQ(size_t, cov.size(), 2); + auto it = cov.find({0,0,0}); + UASSERT(it != cov.end() && it->second); + it = cov.find({0,1,0}); + UASSERT(it != cov.end() && !it->second); + } + + // Now blit it back + std::map modified; + vm.blitBackAll(&modified); + // The lower block data should have been written + UASSERTEQ(size_t, modified.size(), 1); + UASSERTEQ(auto, modified.begin()->first, v3s16(0,0,0)); + UASSERTEQ(auto, map.getNode({0,1,0}).getContent(), t_CONTENT_TORCH); + // The upper one should not! + UASSERTEQ(auto, map.getNode({0,bs,0}).getContent(), CONTENT_AIR); +} diff --git a/src/voxel.h b/src/voxel.h index c47f97a30..a1a3784e5 100644 --- a/src/voxel.h +++ b/src/voxel.h @@ -469,7 +469,7 @@ public: Control */ - virtual void clear(); + void clear(); void print(std::ostream &o, const NodeDefManager *nodemgr, VoxelPrintMode mode=VOXELPRINT_MATERIAL) const; From aa1bab21564cdac01ed2eaed6c2284fc1497626a Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 25 May 2025 15:43:59 +0200 Subject: [PATCH 40/48] Implement API to create empty VoxelManip --- doc/lua_api.md | 33 +++++++++++++++--------- src/script/lua_api/l_vmanip.cpp | 45 +++++++++++++++++++++++++++++---- src/script/lua_api/l_vmanip.h | 1 + 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index cd7707343..3a6da1bdb 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5005,7 +5005,8 @@ A VoxelManip object can be created any time using either: If the optional position parameters are present for either of these routines, the specified region will be pre-loaded into the VoxelManip object on creation. Otherwise, the area of map you wish to manipulate must first be loaded into the -VoxelManip object using `VoxelManip:read_from_map()`. +VoxelManip object using `VoxelManip:read_from_map()`, or an empty one created +with `VoxelManip:initialize()`. Note that `VoxelManip:read_from_map()` returns two position vectors. The region formed by these positions indicate the minimum and maximum (respectively) @@ -5016,14 +5017,14 @@ be queried any time after loading map data with `VoxelManip:get_emerged_area()`. Now that the VoxelManip object is populated with map data, your mod can fetch a copy of this data using either of two methods. `VoxelManip:get_node_at()`, which retrieves an individual node in a MapNode formatted table at the position -requested is the simplest method to use, but also the slowest. +requested. This is the simplest method to use, but also the slowest. Nodes in a VoxelManip object may also be read in bulk to a flat array table using: * `VoxelManip:get_data()` for node content (in Content ID form, see section [Content IDs]), -* `VoxelManip:get_light_data()` for node light levels, and +* `VoxelManip:get_light_data()` for node param (usually light levels), and * `VoxelManip:get_param2_data()` for the node type-dependent "param2" values. See section [Flat array format] for more details. @@ -5038,17 +5039,16 @@ internal state unless otherwise explicitly stated. Once the bulk data has been edited to your liking, the internal VoxelManip state can be set using: -* `VoxelManip:set_data()` for node content (in Content ID form, see section - [Content IDs]), -* `VoxelManip:set_light_data()` for node light levels, and -* `VoxelManip:set_param2_data()` for the node type-dependent `param2` values. +* `VoxelManip:set_data()` or +* `VoxelManip:set_light_data()` or +* `VoxelManip:set_param2_data()` The parameter to each of the above three functions can use any table at all in the same flat array format as produced by `get_data()` etc. and is not required to be a table retrieved from `get_data()`. Once the internal VoxelManip state has been modified to your liking, the -changes can be committed back to the map by calling `VoxelManip:write_to_map()` +changes can be committed back to the map by calling `VoxelManip:write_to_map()`. ### Flat array format @@ -5180,15 +5180,22 @@ inside the VoxelManip. Methods ------- -* `read_from_map(p1, p2)`: Loads a chunk of map into the VoxelManip object +* `read_from_map(p1, p2)`: Loads a part of the map into the VoxelManip object containing the region formed by `p1` and `p2`. - * returns actual emerged `pmin`, actual emerged `pmax` + * returns actual emerged `pmin`, actual emerged `pmax` (MapBlock-aligned) * Note that calling this multiple times will *add* to the area loaded in the VoxelManip, and not reset it. +* `initialize(p1, p2, [node])`: Clears and resizes the VoxelManip object to + comprise the region formed by `p1` and `p2`. + * **No data** is read from the map, so you can use this to treat `VoxelManip` + objects as general containers of node data. + * `node`: if present the data will be filled with this node; if not it will + be uninitialized + * returns actual emerged `pmin`, actual emerged `pmax` (MapBlock-aligned) + * (introduced in 5.13.0) * `write_to_map([light])`: Writes the data loaded from the `VoxelManip` back to the map. - * **important**: data must be set using `VoxelManip:set_data()` before - calling this. + * **important**: you should call `set_data()` before this, or nothing will change. * if `light` is true, then lighting is automatically recalculated. The default value is true. If `light` is false, no light calculations happen, and you should correct @@ -5249,6 +5256,8 @@ Methods 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. + * "Emerged" does not imply that this region was actually loaded from the map, + if `initialize()` has been used. * `close()`: Frees the data buffers associated with the VoxelManip object. It will become empty. * Since Lua's garbage collector is not aware of the potentially significant diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index 09a9f9ffa..af254b105 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -44,7 +44,40 @@ int LuaVoxelManip::l_read_from_map(lua_State *L) push_v3s16(L, vm->m_area.MinEdge); push_v3s16(L, vm->m_area.MaxEdge); + return 2; +} +int LuaVoxelManip::l_initialize(lua_State *L) +{ + MAP_LOCK_REQUIRED; + + LuaVoxelManip *o = checkObject(L, 1); + MMVManip *vm = o->vm; + + if (o->is_mapgen_vm) + throw LuaError("Cannot modify mapgen VoxelManip object"); + + VoxelArea area; + { + v3s16 bp1 = getNodeBlockPos(check_v3s16(L, 2)); + v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 3)); + sortBoxVerticies(bp1, bp2); + area = VoxelArea(bp1 * MAP_BLOCKSIZE, (bp2+1) * MAP_BLOCKSIZE - v3s16(1)); + } + assert(!area.hasEmptyExtent()); + + vm->clear(); + vm->addArea(area); + if (lua_istable(L, 4)) { + MapNode n = readnode(L, 4); + const u32 volume = vm->m_area.getVolume(); + for (u32 i = 0; i != volume; i++) + vm->m_data[i] = n; + vm->clearFlags(vm->m_area, VOXELFLAG_NO_DATA); + } + + push_v3s16(L, vm->m_area.MinEdge); + push_v3s16(L, vm->m_area.MaxEdge); return 2; } @@ -93,11 +126,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. + // Mark all data as present, since we just got it from Lua + // Note that we can't tell if the caller intended to put CONTENT_IGNORE or + // is just repeating the dummy values we push in l_get_data() in case + // VOXELFLAG_NO_DATA is set. In practice this doesn't matter since ignore + // isn't written back to the map anyway. + vm->clearFlags(vm->m_area, VOXELFLAG_NO_DATA); return 0; } @@ -449,6 +483,7 @@ void LuaVoxelManip::Register(lua_State *L) const char LuaVoxelManip::className[] = "VoxelManip"; const luaL_Reg LuaVoxelManip::methods[] = { luamethod(LuaVoxelManip, read_from_map), + luamethod(LuaVoxelManip, initialize), luamethod(LuaVoxelManip, get_data), luamethod(LuaVoxelManip, set_data), luamethod(LuaVoxelManip, get_node_at), diff --git a/src/script/lua_api/l_vmanip.h b/src/script/lua_api/l_vmanip.h index bfd83f56c..95bb82ce2 100644 --- a/src/script/lua_api/l_vmanip.h +++ b/src/script/lua_api/l_vmanip.h @@ -24,6 +24,7 @@ private: static int gc_object(lua_State *L); static int l_read_from_map(lua_State *L); + static int l_initialize(lua_State *L); static int l_get_data(lua_State *L); static int l_set_data(lua_State *L); static int l_write_to_map(lua_State *L); From 9f7501d20a0eda96bbf348a5e4470bc464baccba Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 25 May 2025 19:16:58 +0200 Subject: [PATCH 41/48] Handle writing to unloaded blocks in MMVManip::blitBackAll() This could happen before and would just silently discard the data, but now that Lua can create VManips without loading from map beforehand we definitely need to handle this case. --- src/map.cpp | 37 ++++++++++++++++++++++--------------- src/voxel.cpp | 1 - src/voxel.h | 1 - 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index 1c1dfd51c..964d8b5af 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -789,20 +789,10 @@ void MMVManip::initialEmerge(v3s16 p_min, v3s16 p_max, bool load_if_inexistent) if (auto it = had_blocks.find(p); it != had_blocks.end() && it->second) continue; - MapBlock *block; - bool block_data_inexistent = false; - { - TimeTaker timer2("emerge load", &emerge_load_time); - - block = m_map->getBlockNoCreateNoEx(p); - if (!block) - block_data_inexistent = true; - else - block->copyTo(*this); - } - - if(block_data_inexistent) - { + MapBlock *block = m_map->getBlockNoCreateNoEx(p); + if (block) { + block->copyTo(*this); + } else { if (load_if_inexistent && !blockpos_over_max_limit(p)) { block = m_map->emergeBlock(p, true); assert(block); @@ -862,6 +852,8 @@ void MMVManip::blitBackAll(std::map *modified_blocks, return; assert(m_map); + size_t nload = 0; + // Copy all the blocks with data back to the map const auto loaded_blocks = getCoveredBlocks(); for (auto &it : loaded_blocks) { @@ -869,7 +861,18 @@ void MMVManip::blitBackAll(std::map *modified_blocks, continue; v3s16 p = it.first; MapBlock *block = m_map->getBlockNoCreateNoEx(p); - if (!block || (!overwrite_generated && block->isGenerated())) + if (!block) { + if (!blockpos_over_max_limit(p)) { + block = m_map->emergeBlock(p, true); + nload++; + } + } + if (!block) { + warningstream << "blitBackAll: Couldn't load block " << p + << " to write data to map" << std::endl; + continue; + } + if (!overwrite_generated && block->isGenerated()) continue; block->copyFrom(*this); @@ -879,6 +882,10 @@ void MMVManip::blitBackAll(std::map *modified_blocks, if(modified_blocks) (*modified_blocks)[p] = block; } + + if (nload > 0) { + verbosestream << "blitBackAll: " << nload << " blocks had to be loaded for writing" << std::endl; + } } MMVManip *MMVManip::clone() const diff --git a/src/voxel.cpp b/src/voxel.cpp index 4d0ce84b7..5a69b0725 100644 --- a/src/voxel.cpp +++ b/src/voxel.cpp @@ -15,7 +15,6 @@ Debug stuff */ u64 emerge_time = 0; -u64 emerge_load_time = 0; VoxelManipulator::~VoxelManipulator() { diff --git a/src/voxel.h b/src/voxel.h index a1a3784e5..7fbaadc28 100644 --- a/src/voxel.h +++ b/src/voxel.h @@ -34,7 +34,6 @@ class NodeDefManager; Debug stuff */ extern u64 emerge_time; -extern u64 emerge_load_time; /* This class resembles aabbox3d a lot, but has inclusive From fcddac6c072dd6ee4085a059b02af177c0e0e4da Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 28 May 2025 14:18:06 +0200 Subject: [PATCH 42/48] Formspec: Fix malfunctioning 'Proceed' button on sizeless formspecs --- doc/menu_lua_api.md | 4 +-- src/gui/guiFormSpecMenu.cpp | 49 +++++++++++++++++-------------------- src/gui/guiFormSpecMenu.h | 3 +-- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index 0a067764e..56b51f3b9 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -23,8 +23,8 @@ Callbacks * `core.button_handler(fields)`: called when a button is pressed. * `fields` = `{name1 = value1, name2 = value2, ...}` * `core.event_handler(event)` - * `event`: `"MenuQuit"`, `"KeyEnter"`, `"ExitButton"`, `"EditBoxEnter"` or - `"FullscreenChange"` + * `event`: `"MenuQuit"` (derived from `quit`) or `"FullscreenChange"` + The main menu may issue custom events, such as `"Refresh"` (server list). * `core.on_before_close()`: called before the menu is closed, either to exit or to join a game diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index f1e91bd0b..75832298f 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -77,6 +77,9 @@ return; \ } +// Element ID of the "Proceed" button shown for sizeless formspecs +constexpr s32 ID_PROCEED_BTN = 257; + /* GUIFormSpecMenu */ @@ -2998,7 +3001,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) gui::IGUIElement *focused_element = Environment->getFocus(); if (focused_element && focused_element->getParent() == this) { s32 focused_id = focused_element->getID(); - if (focused_id > 257) { + if (focused_id > ID_PROCEED_BTN) { for (const GUIFormSpecMenu::FieldSpec &field : m_fields) { if (field.fid == focused_id) { m_focused_element = field.fname; @@ -3308,7 +3311,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) size.X / 2 - 70, pos.Y, size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2 ); - GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257, + GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, ID_PROCEED_BTN, wstrgettext("Proceed").c_str()); } } @@ -4031,12 +4034,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) if (m_joystick->wasKeyDown(KeyType::ESC)) { tryClose(); } else if (m_joystick->wasKeyDown(KeyType::JUMP)) { - if (m_allowclose) { - acceptInput(quit_mode_accept); - quitMenu(); - } else { - acceptInput(quit_mode_try); - } + trySubmitClose(); } } return handled; @@ -4056,6 +4054,16 @@ void GUIFormSpecMenu::tryClose() } } +void GUIFormSpecMenu::trySubmitClose() +{ + if (m_allowclose) { + acceptInput(quit_mode_accept); + quitMenu(); + } else { + acceptInput(quit_mode_try); + } +} + bool GUIFormSpecMenu::OnEvent(const SEvent& event) { if (event.EventType==EET_KEY_INPUT_EVENT) { @@ -4099,12 +4107,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) break; } if (current_keys_pending.key_enter) { - if (m_allowclose) { - acceptInput(quit_mode_accept); - quitMenu(); - } else { - acceptInput(quit_mode_try); - } + trySubmitClose(); } else { acceptInput(); } @@ -4848,10 +4851,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) { s32 caller_id = event.GUIEvent.Caller->getID(); - if (caller_id == 257) { - acceptInput(quit_mode_accept); - m_text_dst->gotText(L"ExitButton"); - quitMenu(); + if (caller_id == ID_PROCEED_BTN) { + trySubmitClose(); return true; } @@ -4881,7 +4882,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (s.is_exit) { acceptInput(quit_mode_accept); - m_text_dst->gotText(L"ExitButton"); quitMenu(); return true; } @@ -4931,7 +4931,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { - if (event.GUIEvent.Caller->getID() > 257) { + if (event.GUIEvent.Caller->getID() > ID_PROCEED_BTN) { bool close_on_enter = true; for (GUIFormSpecMenu::FieldSpec &s : m_fields) { if (s.ftype == f_Unknown && @@ -4948,12 +4948,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) current_keys_pending.key_enter = true; if (close_on_enter) { - if (m_allowclose) { - acceptInput(quit_mode_accept); - quitMenu(); - } else { - acceptInput(quit_mode_try); - } + trySubmitClose(); } else { acceptInput(); } @@ -4963,7 +4958,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) { int current_id = event.GUIEvent.Caller->getID(); - if (current_id > 257) { + if (current_id > ID_PROCEED_BTN) { // find the element that was clicked for (GUIFormSpecMenu::FieldSpec &s : m_fields) { // if it's a table, set the send field diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 8dbfcab93..f279bbb29 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -68,8 +68,6 @@ struct TextDest { virtual ~TextDest() = default; - // This is deprecated I guess? -celeron55 - virtual void gotText(const std::wstring &text) {} virtual void gotText(const StringMap &fields) = 0; std::string m_formname; @@ -493,6 +491,7 @@ private: bool parseMiddleRect(const std::string &value, core::rect *parsed_rect); void tryClose(); + void trySubmitClose(); void showTooltip(const std::wstring &text, const irr::video::SColor &color, const irr::video::SColor &bgcolor); From 660b1cf9bf2d395c02ece92404ee3c7fd6f71c43 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 28 May 2025 14:44:24 +0200 Subject: [PATCH 43/48] Formspec: remove gotText(wstr) remains These functions are now unused (no caller). --- src/client/game_formspec.cpp | 10 ---------- src/gui/guiEngine.cpp | 6 ------ src/gui/guiEngine.h | 6 ------ 3 files changed, 22 deletions(-) diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp index dc51247b0..3d8dc6fc8 100644 --- a/src/client/game_formspec.cpp +++ b/src/client/game_formspec.cpp @@ -31,16 +31,6 @@ struct TextDestNodeMetadata : public TextDest m_p = p; m_client = client; } - // This is deprecated I guess? -celeron55 - void gotText(const std::wstring &text) - { - std::string ntext = wide_to_utf8(text); - infostream << "Submitting 'text' field of node at (" << m_p.X << "," - << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; - StringMap fields; - fields["text"] = ntext; - m_client->sendNodemetaFields(m_p, "", fields); - } void gotText(const StringMap &fields) { m_client->sendNodemetaFields(m_p, "", fields); diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index b31f12ef9..0ec0d7a76 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -40,12 +40,6 @@ void TextDestGuiEngine::gotText(const StringMap &fields) m_engine->getScriptIface()->handleMainMenuButtons(fields); } -/******************************************************************************/ -void TextDestGuiEngine::gotText(const std::wstring &text) -{ - m_engine->getScriptIface()->handleMainMenuEvent(wide_to_utf8(text)); -} - /******************************************************************************/ MenuTextureSource::~MenuTextureSource() { diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index 5c95e9a4c..f4b22f3c2 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -61,12 +61,6 @@ public: */ void gotText(const StringMap &fields); - /** - * receive text/events transmitted by guiFormSpecMenu - * @param text textual representation of event - */ - void gotText(const std::wstring &text); - private: /** target to transmit data to */ GUIEngine *m_engine = nullptr; From 535d7575637a41fa0d792c9f05c23cb1a75919c2 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 28 May 2025 14:30:40 +0200 Subject: [PATCH 44/48] Formspec: Move GUI event handling to switch/case --- src/gui/guiFormSpecMenu.cpp | 116 ++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 75832298f..97d86b05f 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -4821,12 +4821,18 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } if (event.EventType == EET_GUI_EVENT) { - if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED - && isVisible()) { - // find the element that was clicked + const s32 caller_id = event.GUIEvent.Caller->getID(); + bool close_on_enter; + + switch (event.GUIEvent.EventType) { + case gui::EGET_TAB_CHANGED: + if (!isVisible()) + break; + + // find the element that was clicked for (GUIFormSpecMenu::FieldSpec &s : m_fields) { - if ((s.ftype == f_TabHeader) && - (s.fid == event.GUIEvent.Caller->getID())) { + if (s.ftype == f_TabHeader && + s.fid == caller_id) { if (!s.sound.empty() && m_sound_manager) m_sound_manager->playSound(0, SoundSpec(s.sound, 1.0f)); s.send = true; @@ -4835,22 +4841,24 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) return true; } } - } - if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST - && isVisible()) { + break; + + case gui::EGET_ELEMENT_FOCUS_LOST: + if (!isVisible()) + break; + if (!canTakeFocus(event.GUIEvent.Element)) { infostream<<"GUIFormSpecMenu: Not allowing focus change." <getID(); + break; + case gui::EGET_BUTTON_CLICKED: + case gui::EGET_CHECKBOX_CHANGED: + case gui::EGET_COMBO_BOX_CHANGED: + case gui::EGET_SCROLL_BAR_CHANGED: if (caller_id == ID_PROCEED_BTN) { trySubmitClose(); return true; @@ -4922,55 +4930,57 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) s.send = false; } } - } - if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) { - // move scroll_containers - for (const std::pair &c : m_scroll_containers) - c.second->onScrollEvent(event.GUIEvent.Caller); - } + if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) { + // move scroll_containers + for (const std::pair &c : m_scroll_containers) + c.second->onScrollEvent(event.GUIEvent.Caller); + } + break; - if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { - if (event.GUIEvent.Caller->getID() > ID_PROCEED_BTN) { - bool close_on_enter = true; - for (GUIFormSpecMenu::FieldSpec &s : m_fields) { - if (s.ftype == f_Unknown && - s.fid == event.GUIEvent.Caller->getID()) { - current_field_enter_pending = s.fname; - auto it = field_close_on_enter.find(s.fname); - if (it != field_close_on_enter.end()) - close_on_enter = (*it).second; + case gui::EGET_EDITBOX_ENTER: + if (caller_id <= ID_PROCEED_BTN) + break; - break; - } + close_on_enter = true; + for (GUIFormSpecMenu::FieldSpec &s : m_fields) { + if (s.ftype == f_Unknown && + s.fid == caller_id) { + current_field_enter_pending = s.fname; + auto it = field_close_on_enter.find(s.fname); + if (it != field_close_on_enter.end()) + close_on_enter = (*it).second; + + break; } + } - current_keys_pending.key_enter = true; + current_keys_pending.key_enter = true; - if (close_on_enter) { - trySubmitClose(); - } else { + if (close_on_enter) + trySubmitClose(); + else + acceptInput(); + return true; + + case gui::EGET_TABLE_CHANGED: + if (caller_id <= ID_PROCEED_BTN) + break; + + // find the element that was clicked + for (GUIFormSpecMenu::FieldSpec &s : m_fields) { + // if it's a table, set the send field + // so lua knows which table was changed + if (s.ftype == f_Table && s.fid == caller_id) { + s.send = true; acceptInput(); + s.send = false; } - return true; } - } + return true; - if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) { - int current_id = event.GUIEvent.Caller->getID(); - if (current_id > ID_PROCEED_BTN) { - // find the element that was clicked - for (GUIFormSpecMenu::FieldSpec &s : m_fields) { - // if it's a table, set the send field - // so lua knows which table was changed - if ((s.ftype == f_Table) && (s.fid == current_id)) { - s.send = true; - acceptInput(); - s.send=false; - } - } - return true; - } + default: + break; } } From a2460df3160f671ae10789d2e49d7f9898de3aa5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 30 May 2025 20:14:26 +0200 Subject: [PATCH 45/48] Print Luanti version after ascii art --- src/server.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index a1263ef1d..e89b9441d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -586,14 +586,16 @@ void Server::start() R"(| | |_| | (_| | | | | |_| |)", R"(|_|\__,_|\__,_|_| |_|\__|_|)", }; - if (!m_admin_chat) { // we're not printing to rawstream to avoid it showing up in the logs. // however it would then mess up the ncurses terminal (m_admin_chat), // so we skip it in that case. - for (auto line : art) - std::cerr << line << std::endl; + for (size_t i = 0; i < ARRLEN(art); ++i) + std::cerr << art[i] << (i == ARRLEN(art) - 1 ? "" : "\n"); + // add a "tail" with the engine version + std::cerr << " ___ " << g_version_hash << std::endl; } + actionstream << "World at [" << m_path_world << "]" << std::endl; actionstream << "Server for gameid=\"" << m_gamespec.id << "\" listening on "; From 56a7f0b7cf9943acd7c649fb018d484344e5b9a5 Mon Sep 17 00:00:00 2001 From: grorp Date: Sun, 1 Jun 2025 15:24:14 +0200 Subject: [PATCH 46/48] Don't break when multiple dialogs are shown on startup (#16204) --- builtin/fstk/ui.lua | 2 +- builtin/mainmenu/dlg_rebind_keys.lua | 20 ++++++++++---------- builtin/mainmenu/dlg_reinstall_mtg.lua | 26 +++++++++++++------------- builtin/mainmenu/init.lua | 8 ++++++-- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua index 44d2ab3d8..31b104ed3 100644 --- a/builtin/fstk/ui.lua +++ b/builtin/fstk/ui.lua @@ -118,7 +118,7 @@ function ui.update() if (active_toplevel_ui_elements > 1) then core.log("warning", "more than one active ui ".. - "element, self most likely isn't intended") + "element, this most likely isn't intended") end if (active_toplevel_ui_elements == 0) then diff --git a/builtin/mainmenu/dlg_rebind_keys.lua b/builtin/mainmenu/dlg_rebind_keys.lua index d1b442004..ec4d1357f 100644 --- a/builtin/mainmenu/dlg_rebind_keys.lua +++ b/builtin/mainmenu/dlg_rebind_keys.lua @@ -33,13 +33,13 @@ end local function buttonhandler(this, fields) if fields.reconfigure then + local parent = this.parent + close_dialog(this) - local maintab = ui.find_by_name("maintab") - local dlg = create_settings_dlg("controls_keyboard_and_mouse") - dlg:set_parent(maintab) - maintab:hide() + dlg:set_parent(parent) + parent:hide() dlg:show() return true @@ -74,7 +74,7 @@ local function create_rebind_keys_dlg() return dlg end -function migrate_keybindings() +function migrate_keybindings(parent) -- Show migration dialog if the user upgraded from an earlier version -- and this has not yet been shown before, *or* if keys settings had to be changed if core.is_first_run then @@ -95,14 +95,14 @@ function migrate_keybindings() end if not has_migration then - return + return parent end - local maintab = ui.find_by_name("maintab") - local dlg = create_rebind_keys_dlg() - dlg:set_parent(maintab) - maintab:hide() + dlg:set_parent(parent) + parent:hide() dlg:show() ui.update() + + return dlg end diff --git a/builtin/mainmenu/dlg_reinstall_mtg.lua b/builtin/mainmenu/dlg_reinstall_mtg.lua index c167b2656..6c512ea03 100644 --- a/builtin/mainmenu/dlg_reinstall_mtg.lua +++ b/builtin/mainmenu/dlg_reinstall_mtg.lua @@ -11,7 +11,7 @@ local SETTING_NAME = "no_mtg_notification" -function check_reinstall_mtg() +function check_reinstall_mtg(parent) -- used to be in minetest.conf if core.settings:get_bool(SETTING_NAME) then cache_settings:set_bool(SETTING_NAME, true) @@ -19,14 +19,14 @@ function check_reinstall_mtg() end if cache_settings:get_bool(SETTING_NAME) then - return + return parent end local games = core.get_games() for _, game in ipairs(games) do if game.id == "minetest" then cache_settings:set_bool(SETTING_NAME, true) - return + return parent end end @@ -40,16 +40,16 @@ function check_reinstall_mtg() end if not mtg_world_found then cache_settings:set_bool(SETTING_NAME, true) - return + return parent end - local maintab = ui.find_by_name("maintab") - local dlg = create_reinstall_mtg_dlg() - dlg:set_parent(maintab) - maintab:hide() + dlg:set_parent(parent) + parent:hide() dlg:show() ui.update() + + return dlg end local function get_formspec(dialogdata) @@ -74,22 +74,22 @@ end local function buttonhandler(this, fields) if fields.reinstall then + local parent = this.parent + -- Don't set "no_mtg_notification" here so that the dialog will be shown -- again if downloading MTG fails for whatever reason. this:delete() - local maintab = ui.find_by_name("maintab") - local dlg = create_contentdb_dlg(nil, "minetest/minetest") - dlg:set_parent(maintab) - maintab:hide() + dlg:set_parent(parent) + parent:hide() dlg:show() return true end if fields.dismiss then - cache_settings:set_bool("no_mtg_notification", true) + cache_settings:set_bool(SETTING_NAME, true) this:delete() return true end diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index 14185a484..c1bbc0ae4 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -112,8 +112,12 @@ local function init_globals() tv_main:show() ui.update() - check_reinstall_mtg() - migrate_keybindings() + -- synchronous, chain parents to only show one at a time + local parent = tv_main + parent = migrate_keybindings(parent) + check_reinstall_mtg(parent) + + -- asynchronous, will only be shown if we're still on "maintab" check_new_version() end From 0bb87eb1ff891a0f8ede8bb4fcef1967284659ef Mon Sep 17 00:00:00 2001 From: JosiahWI <41302989+JosiahWI@users.noreply.github.com> Date: Sun, 1 Jun 2025 08:24:32 -0500 Subject: [PATCH 47/48] Avoid signal-unsafe operations in POSIX signal handler (#16160) --- src/client/clientlauncher.cpp | 8 ++++---- src/client/game.cpp | 24 +++++++++++++----------- src/client/game.h | 3 ++- src/gui/guiEngine.cpp | 4 +++- src/gui/guiEngine.h | 6 ++++-- src/main.cpp | 8 ++++---- src/porting.cpp | 22 ++++++++++------------ src/porting.h | 3 ++- src/server.cpp | 4 +++- src/server.h | 3 ++- src/terminal_chat_console.h | 10 ++++++---- 11 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 64f2e8f51..4c8eaf060 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -147,8 +147,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) /* Menu-game loop */ - bool retval = true; - bool *kill = porting::signal_handler_killstatus(); + bool retval = true; + volatile auto *kill = porting::signal_handler_killstatus(); while (m_rendering_engine->run() && !*kill && !g_gamecallback->shutdown_requested) { @@ -540,9 +540,9 @@ bool ClientLauncher::launch_game(std::string &error_message, void ClientLauncher::main_menu(MainMenuData *menudata) { - bool *kill = porting::signal_handler_killstatus(); + volatile auto *kill = porting::signal_handler_killstatus(); video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); - auto *device = m_rendering_engine->get_raw_device(); + auto *device = m_rendering_engine->get_raw_device(); // Wait until app is in foreground because of #15883 infostream << "Waiting for app to be in foreground" << std::endl; diff --git a/src/client/game.cpp b/src/client/game.cpp index 2c9c4fb77..f9a52c43f 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -65,6 +65,8 @@ #include "client/sound/sound_openal.h" #endif +#include + class NodeDugEvent : public MtEvent { public: @@ -561,7 +563,7 @@ public: Game(); ~Game(); - bool startup(bool *kill, + bool startup(volatile std::sig_atomic_t *kill, InputHandler *input, RenderingEngine *rendering_engine, const GameStartData &game_params, @@ -793,14 +795,14 @@ private: This class does take ownership/responsibily for cleaning up etc of any of these items (e.g. device) */ - IrrlichtDevice *device; - RenderingEngine *m_rendering_engine; - video::IVideoDriver *driver; - scene::ISceneManager *smgr; - bool *kill; - std::string *error_message; - bool *reconnect_requested; - PausedNodesList paused_animated_nodes; + IrrlichtDevice *device; + RenderingEngine *m_rendering_engine; + video::IVideoDriver *driver; + scene::ISceneManager *smgr; + volatile std::sig_atomic_t *kill; + std::string *error_message; + bool *reconnect_requested; + PausedNodesList paused_animated_nodes; bool simple_singleplayer_mode; /* End 'cache' */ @@ -932,7 +934,7 @@ Game::~Game() m_rendering_engine->finalize(); } -bool Game::startup(bool *kill, +bool Game::startup(volatile std::sig_atomic_t *kill, InputHandler *input, RenderingEngine *rendering_engine, const GameStartData &start_data, @@ -4235,7 +4237,7 @@ void Game::readSettings() ****************************************************************************/ /****************************************************************************/ -void the_game(bool *kill, +void the_game(volatile std::sig_atomic_t *kill, InputHandler *input, RenderingEngine *rendering_engine, const GameStartData &start_data, diff --git a/src/client/game.h b/src/client/game.h index 6b838e2bc..95976ac7b 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -6,6 +6,7 @@ #include "irrlichttypes.h" #include "config.h" +#include #include #if !IS_CLIENT_BUILD @@ -36,7 +37,7 @@ struct CameraOrientation { #define GAME_FALLBACK_TIMEOUT 1.8f #define GAME_CONNECTION_TIMEOUT 10.0f -void the_game(bool *kill, +void the_game(volatile std::sig_atomic_t *kill, InputHandler *input, RenderingEngine *rendering_engine, const GameStartData &start_data, diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 0ec0d7a76..e843b5b38 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -33,6 +33,8 @@ #include "client/sound/sound_openal.h" #endif +#include + /******************************************************************************/ void TextDestGuiEngine::gotText(const StringMap &fields) @@ -104,7 +106,7 @@ GUIEngine::GUIEngine(JoystickController *joystick, RenderingEngine *rendering_engine, IMenuManager *menumgr, MainMenuData *data, - bool &kill) : + volatile std::sig_atomic_t &kill) : m_rendering_engine(rendering_engine), m_parent(parent), m_menumanager(menumgr), diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index f4b22f3c2..4fde215fb 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -14,6 +14,8 @@ #include "util/enriched_string.h" #include "translation.h" +#include + /******************************************************************************/ /* Structs and macros */ /******************************************************************************/ @@ -124,7 +126,7 @@ public: RenderingEngine *rendering_engine, IMenuManager *menumgr, MainMenuData *data, - bool &kill); + volatile std::sig_atomic_t &kill); /** default destructor */ virtual ~GUIEngine(); @@ -193,7 +195,7 @@ private: irr_ptr m_menu; /** reference to kill variable managed by SIGINT handler */ - bool &m_kill; + volatile std::sig_atomic_t &m_kill; /** variable used to abort menu and return back to main game handling */ bool m_startgame = false; diff --git a/src/main.cpp b/src/main.cpp index 95a2f7be4..51069cf19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1138,7 +1138,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & return false; } ChatInterface iface; - bool &kill = *porting::signal_handler_killstatus(); + volatile auto &kill = *porting::signal_handler_killstatus(); try { // Create server @@ -1181,7 +1181,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & server.start(); // Run server - bool &kill = *porting::signal_handler_killstatus(); + volatile auto &kill = *porting::signal_handler_killstatus(); dedicated_server_loop(server, kill); } catch (const ModError &e) { @@ -1226,7 +1226,7 @@ static bool migrate_map_database(const GameParams &game_params, const Settings & u32 count = 0; u64 last_update_time = 0; - bool &kill = *porting::signal_handler_killstatus(); + volatile auto &kill = *porting::signal_handler_killstatus(); std::vector blocks; old_db->listAllLoadableBlocks(blocks); @@ -1280,7 +1280,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting u32 count = 0; u64 last_update_time = 0; - bool &kill = *porting::signal_handler_killstatus(); + volatile auto &kill = *porting::signal_handler_killstatus(); const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE; const s16 map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9); diff --git a/src/porting.cpp b/src/porting.cpp index 11ff63a88..711b65db6 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -60,6 +60,7 @@ #include "util/string.h" #include "util/tracy_wrapper.h" #include +#include #include #include #include @@ -81,31 +82,28 @@ namespace porting Signal handler (grabs Ctrl-C on POSIX systems) */ -static bool g_killed = false; +volatile static std::sig_atomic_t g_killed = false; -bool *signal_handler_killstatus() +volatile std::sig_atomic_t *signal_handler_killstatus() { return &g_killed; } #if !defined(_WIN32) // POSIX +#define STDERR_FILENO 2 static void signal_handler(int sig) { if (!g_killed) { if (sig == SIGINT) { - dstream << "INFO: signal_handler(): " - << "Ctrl-C pressed, shutting down." << std::endl; + const char *dbg_text{"INFO: signal_handler(): " + "Ctrl-C pressed, shutting down.\n"}; + write(STDERR_FILENO, dbg_text, strlen(dbg_text)); } else if (sig == SIGTERM) { - dstream << "INFO: signal_handler(): " - << "got SIGTERM, shutting down." << std::endl; + const char *dbg_text{"INFO: signal_handler(): " + "got SIGTERM, shutting down.\n"}; + write(STDERR_FILENO, dbg_text, strlen(dbg_text)); } - - // Comment out for less clutter when testing scripts - /*dstream << "INFO: sigint_handler(): " - << "Printing debug stacks" << std::endl; - debug_stacks_print();*/ - g_killed = true; } else { (void)signal(sig, SIG_DFL); diff --git a/src/porting.h b/src/porting.h index f7d623d33..1a4bb9e7b 100644 --- a/src/porting.h +++ b/src/porting.h @@ -13,6 +13,7 @@ #endif // Be mindful of what you include here! +#include #include #include "config.h" #include "irrlichttypes.h" // u64 @@ -77,7 +78,7 @@ namespace porting void signal_handler_init(); // Returns a pointer to a bool. // When the bool is true, program should quit. -[[nodiscard]] bool *signal_handler_killstatus(); +[[nodiscard]] volatile std::sig_atomic_t *signal_handler_killstatus(); /* Path of static data directory. diff --git a/src/server.cpp b/src/server.cpp index e89b9441d..89bba75fb 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -65,6 +65,8 @@ #include "gettext.h" #include "util/tracy_wrapper.h" +#include + class ClientNotFoundException : public BaseException { public: @@ -4114,7 +4116,7 @@ std::unique_ptr Server::emergePlayer(const char *name, session_t peer return playersao; } -void dedicated_server_loop(Server &server, bool &kill) +void dedicated_server_loop(Server &server, volatile std::sig_atomic_t &kill) { verbosestream<<"dedicated_server_loop()"< +#include #include #include #include @@ -799,4 +800,4 @@ private: Shuts down when kill is set to true. */ -void dedicated_server_loop(Server &server, bool &kill); +void dedicated_server_loop(Server &server, volatile std::sig_atomic_t &kill); diff --git a/src/terminal_chat_console.h b/src/terminal_chat_console.h index f49557d0c..3324656dc 100644 --- a/src/terminal_chat_console.h +++ b/src/terminal_chat_console.h @@ -9,6 +9,8 @@ #include "util/container.h" #include "log.h" #include "log_internal.h" + +#include #include #include @@ -45,7 +47,7 @@ public: void setup( ChatInterface *iface, - bool *kill_requested, + volatile std::sig_atomic_t *kill_requested, const std::string &nick) { m_nick = nick; @@ -96,9 +98,9 @@ private: int m_rows; bool m_can_draw_text; - bool *m_kill_requested = nullptr; - ChatBackend m_chat_backend; - ChatInterface *m_chat_interface; + volatile std::sig_atomic_t *m_kill_requested = nullptr; + ChatBackend m_chat_backend; + ChatInterface *m_chat_interface; TermLogOutput m_log_output; From fde6384a099143a4a3033e090d61296b1d15f051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Sun, 1 Jun 2025 23:21:35 +0200 Subject: [PATCH 48/48] Fix and clean up skeletal animation (#15722) * Fix attachments lagging behind their parents (#14818) * Fix animation blending (#14817) * Bring back cool guy as another .x smoke test * Add .x mesh loader unittest * Do bounding box & matrix calculation at proper point in time * Remove obsolete `SAnimatedMesh` --- doc/lua_api.md | 3 + .../mods/testentities/models/LICENSE.txt | 8 +- .../models/testentities_cool_guy.png | Bin 0 -> 3673 bytes .../models/testentities_cool_guy.x | 2 + games/devtest/mods/testentities/visuals.lua | 13 + irr/include/IAnimatedMesh.h | 22 +- irr/include/IAnimatedMeshSceneNode.h | 47 +- irr/include/IBoneSceneNode.h | 88 +-- irr/include/IMesh.h | 36 +- irr/include/IMeshManipulator.h | 20 - irr/include/IMeshSceneNode.h | 2 +- irr/include/ISceneNode.h | 14 +- irr/include/SAnimatedMesh.h | 167 ----- irr/include/SMesh.h | 13 +- irr/include/SkinnedMesh.h | 146 ++-- irr/include/Transform.h | 42 ++ irr/include/quaternion.h | 2 +- irr/src/CAnimatedMeshSceneNode.cpp | 337 ++++----- irr/src/CAnimatedMeshSceneNode.h | 44 +- irr/src/CB3DMeshFileLoader.cpp | 34 +- irr/src/CBoneSceneNode.cpp | 86 --- irr/src/CBoneSceneNode.h | 71 +- irr/src/CGLTFMeshFileLoader.cpp | 46 +- irr/src/CMakeLists.txt | 1 - irr/src/CMeshCache.cpp | 8 +- irr/src/CMeshManipulator.cpp | 30 - irr/src/CMeshManipulator.h | 9 - irr/src/CNullDriver.cpp | 2 +- irr/src/COBJMeshFileLoader.cpp | 21 +- irr/src/CSceneManager.cpp | 1 - irr/src/CXMeshFileLoader.cpp | 12 +- irr/src/SkinnedMesh.cpp | 656 +++++++----------- lib/tiniergltf/tiniergltf.hpp | 7 +- src/client/content_cao.cpp | 77 +- src/client/content_cao.h | 2 - src/client/mesh.cpp | 10 +- src/nodedef.cpp | 8 - src/unittest/CMakeLists.txt | 2 + src/unittest/test_irr_gltf_mesh_loader.cpp | 44 +- src/unittest/test_irr_x_mesh_loader.cpp | 111 +++ 40 files changed, 856 insertions(+), 1388 deletions(-) create mode 100644 games/devtest/mods/testentities/models/testentities_cool_guy.png create mode 100755 games/devtest/mods/testentities/models/testentities_cool_guy.x delete mode 100644 irr/include/SAnimatedMesh.h create mode 100644 irr/include/Transform.h delete mode 100644 irr/src/CBoneSceneNode.cpp create mode 100644 src/unittest/test_irr_x_mesh_loader.cpp diff --git a/doc/lua_api.md b/doc/lua_api.md index 3a6da1bdb..07f70c36f 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -315,6 +315,9 @@ due to their space savings. Bone weights should be normalized, e.g. using ["normalize all" in Blender](https://docs.blender.org/manual/en/4.2/grease_pencil/modes/weight_paint/weights_menu.html#normalize-all). +Note that nodes using matrix transforms must not be animated. +This also extends to bone overrides, which must not be applied to them. + You can use the [Khronos glTF validator](https://github.com/KhronosGroup/glTF-Validator) to check whether a model is a valid glTF file. diff --git a/games/devtest/mods/testentities/models/LICENSE.txt b/games/devtest/mods/testentities/models/LICENSE.txt index 19ffffc5c..6ec445db6 100644 --- a/games/devtest/mods/testentities/models/LICENSE.txt +++ b/games/devtest/mods/testentities/models/LICENSE.txt @@ -12,4 +12,10 @@ Jordach (CC BY-SA 3.0): Zeg9 (CC BY-SA 3.0): testentities_lava_flan.x - testentities_lava_flan.png \ No newline at end of file + testentities_lava_flan.png + +"Cool Guy": + +hecks (refer to irr/LICENSE): + testentities_cool_guy.x + testentities_cool_guy.png diff --git a/games/devtest/mods/testentities/models/testentities_cool_guy.png b/games/devtest/mods/testentities/models/testentities_cool_guy.png new file mode 100644 index 0000000000000000000000000000000000000000..84cc12e442b73df3583daed7fe9c5433b86c5749 GIT binary patch literal 3673 zcmd6qXHeA5md5`>90o~}Gl&StAUOz!nB14p%GeZ0aIHU>mG&^jGYd(mA{u>)a*y@H(8g*X6!e4Ffa#$1++=q z70I+EM+qUxQ3{wS2?1e&NHH-IKFw&pN$^{FK#oI>L#2q)x{wmwv!^h)<* z33zDJ8Oo7MbNFs^M`)HEx9d9MDz=?%DPf_*F}AYvf_FukRnd05z+!Sg_4umbrw6~E z7#}H`%+_<)7Qd^QXx@{t@0rS6?GLb-Mew#j%mQh;% z5tQprzF5k*^6rDjv$mOPYbKQZLNk~wCfE#&%mH2qG+ z(1xPlteMPUeF5BPrqXQMqcAMlU4p*v{;6kvF;DUf7Nr~o>!cn)MMb3e+G(f>epDj= z3bh!L%lTu)efVfhU#z7}?4$|vU^X)$q1>+LW2M7Dru@rLOtl*o6;%;q^W(P1#3lGj zN=j{QZJXnzm4>Dg?$ZI-!5l8NxRMwCZqJ^j^jLPUmz6xtp?KX)K`8V|@hJZKP8hpb zD7Hv-S$V-!w?vyHS`s691^+bJ6fImO_j7P~)*IJ6VX0##=`q_oFp$MrTocH4mcEs~ zWq{I=4L;E-YHNE#!Tunpu(XL5_E0QwZ5q{0$ zC^!lXb<%bDf8Dq~k)LY{*k8gD&$;-=2V$b;=&H*Ycikik2{7Cs8-M+l)d-q+! z>ir81leRT;^&EZR70+8?mwnxy=leTpn&Gm<6l}@N=IcS%SLgZKX};1K`VWU2Ifs*n zvR>!r278C$yicJRHDuN@rxu)C*6(4qnB=*h*!wXm>|v|X&B z_Zw!m%uPCH86M^0t$|(nK5KEvR`WVvJ|3Pfr}nU_d`b9fgNUVo3e5iC zIg@x8`=b$833eHHuhH@$b_j{PJmul$t|h?w*Y)8SaRy5^DzmF<{auahLH&fd}nF7sg-B3MV#MOWCO&T_m$|;mQoC zV>DXdOlo)?Z;ZiZDfzH!{SJGLtyVsJXFm}-Tm+k8i(x}qA45`8YS4FWdef2?qKGTo zmKh7zRg#=C>Aws)4eiV}y)YFK|Fya^Q(qH`3wk{?KdB_%K}3I5?z>@tpfLV~7OzSqbq{Uw!e74Vx|&R%YOTIXAx|CI#Wh4s)4(}PIS7%<MOA+02|$40m-c>mJPZ{(9EYLNBMh&PG?^@TVWX8UF17t?y*8sWd*p#5Z~ zoc*g|aPhI--S%ARTb%DSEjnjyWmN)M=+~auSdkhO3Req|c_@i4xmf1IWFZJojk}yQ z!2c~#gqx@@1VL~ofd5W%n#BCNJeP z{~@jd@)<81XsB*}!av?_CD?j0fx$iR9=(OOwsCMGc?6eAK0V3>coCnY8ouSo#f7(e zx2O9>C z*oxZh4RTtMsqX23Etm|FxSv(|?N3>+V+2~R8D%!7KY_k?@f+~2<}v1w7m!5T8Q*<+ zT`{~L`!y38VqVuuz12NWd+O(TvG;osRQ^stCpE!;ruE#cyyx?|Z;5*3kY=#HLg?g% zr?lMEfF;n|)8FwrvF7m6M^K{Eg<&tVsCNkYhA1KE=YZ4H1Sfq<<4E@a6=+gK2phBr zrmJP_gj_H_s@@;c<+^x4QfNO^QHmxWHc{6!vHJZ;fme$~&*rJ}ijod%OJ-*IaYXa8 zJ6Wue$i6|^OhWwUwCvE2O@_Zlm08)hlWaKaMY^q2B@752%F+JgO@cfzz4M21B*0Iv zWb-So8@Ny9smQ&2>vF{enpi7&-*lyDW2^kd{7asO$6_uR;SXc=rrkAT9F=dE@G6>; zC{Dx3RK2DO3HljRCQ>3Z`i?m-_Txuf4v93SQQN%MW4HXb%v5^eyT=tNUAjlZ6^&3L z`TXQO=v@>py>|4)%RG5!OgQCb{F1G@@;xW>&%@`f-P94=2|Dzh8Ul_z-#~TW(@@#p zFF??s`&@0?aez8S-MK@8Qv@D;5{qLi=EBl@=e zc>(k;iZs|OmtLAp{mSOucc*RdLHskl-hS){Yj8Nh1EQRR34!+Vv`m-wPEIX`Uu{3nn?k>i#km6bCvj*~I6?c-Kx8hcs1?$BCyO zaxpNxDZ*gLN3@(W;wizGMN5+!t`3`2$;yE#e&a=9iDa~z@bMf`y~`T-qf+;08fr_g z9_&+|FX2YSF1N`XUQMIyY;6uIQkp9iysZuU_gXIU9+WK|e(>;Tp-Txi;XvWrTBA-p z%L1-0*34%SiPk7ads*ifm6#Z!X(_~()^rR$FLSVD$6cst@ydi3I|6S$2%rd6kYJr2 z#eExcV$3sNoE=jyzqD?OBk188iSr02N*;q7Y2Auw-tl4*c>>V1K+~c{Bd!`NY;4wk z0hL$*&#t0rUgg3eUkBrtRdy z?fYX$m(eZkW z?W?8R9S(8#SD=wCH~9aw1v;bqS7!eS{co-K+u6Sj0{^q$|K={SoHBh|gr|C2!JjW9 OprN9p{PnR_*na>&K!GLz literal 0 HcmV?d00001 diff --git a/games/devtest/mods/testentities/models/testentities_cool_guy.x b/games/devtest/mods/testentities/models/testentities_cool_guy.x new file mode 100755 index 000000000..e806d8315 --- /dev/null +++ b/games/devtest/mods/testentities/models/testentities_cool_guy.x @@ -0,0 +1,2 @@ +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/games/devtest/mods/testentities/visuals.lua b/games/devtest/mods/testentities/visuals.lua index dfbf655ea..26b538e7f 100644 --- a/games/devtest/mods/testentities/visuals.lua +++ b/games/devtest/mods/testentities/visuals.lua @@ -102,6 +102,19 @@ core.register_entity("testentities:lava_flan", { end, }) +core.register_entity("testentities:cool_guy", { + initial_properties = { + visual = "mesh", + mesh = "testentities_cool_guy.x", + textures = { + "testentities_cool_guy.png" + }, + }, + on_activate = function(self) + self.object:set_animation({x = 0, y = 29}, 30, 0, true) + end, +}) + -- Advanced visual tests -- An entity for testing animated and yaw-modulated sprites diff --git a/irr/include/IAnimatedMesh.h b/irr/include/IAnimatedMesh.h index 62568bf6b..0ef0971bd 100644 --- a/irr/include/IAnimatedMesh.h +++ b/irr/include/IAnimatedMesh.h @@ -13,8 +13,8 @@ namespace scene //! Interface for an animated mesh. /** There are already simple implementations of this interface available so you don't have to implement this interface on your own if you need to: -You might want to use irr::scene::SAnimatedMesh, irr::scene::SMesh, -irr::scene::SMeshBuffer etc. */ +You might want to use irr::scene::SMesh, irr::scene::SMeshBuffer etc. +*/ class IAnimatedMesh : public IMesh { public: @@ -34,22 +34,8 @@ public: scene node the mesh is instantiated in.*/ virtual void setAnimationSpeed(f32 fps) = 0; - //! Returns the IMesh interface for a frame. - /** \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. - This is useful for making a safe downcast. For example, - if getMeshType() returns EAMT_MD2 it's safe to cast the - IAnimatedMesh to IAnimatedMeshMD2. - \returns Type of the mesh. */ - E_ANIMATED_MESH_TYPE getMeshType() const override - { - return EAMT_UNKNOWN; - } + //! Returns the type of the animated mesh. Useful for safe downcasts. + E_ANIMATED_MESH_TYPE getMeshType() const = 0; }; } // end namespace scene diff --git a/irr/include/IAnimatedMeshSceneNode.h b/irr/include/IAnimatedMeshSceneNode.h index 8f9f6d661..2df8da917 100644 --- a/irr/include/IAnimatedMeshSceneNode.h +++ b/irr/include/IAnimatedMeshSceneNode.h @@ -12,35 +12,8 @@ namespace irr { namespace scene { -enum E_JOINT_UPDATE_ON_RENDER -{ - //! do nothing - EJUOR_NONE = 0, - - //! get joints positions from the mesh (for attached nodes, etc) - EJUOR_READ, - - //! control joint positions in the mesh (eg. ragdolls, or set the animation from animateJoints() ) - EJUOR_CONTROL -}; - class IAnimatedMeshSceneNode; -//! Callback interface for catching events of ended animations. -/** Implement this interface and use -IAnimatedMeshSceneNode::setAnimationEndCallback to be able to -be notified if an animation playback has ended. -**/ -class IAnimationEndCallBack : public virtual IReferenceCounted -{ -public: - //! Will be called when the animation playback has ended. - /** See IAnimatedMeshSceneNode::setAnimationEndCallback for - more information. - \param node: Node of which the animation has ended. */ - virtual void OnAnimationEnd(IAnimatedMeshSceneNode *node) = 0; -}; - //! Scene node capable of displaying an animated mesh. class IAnimatedMeshSceneNode : public ISceneNode { @@ -120,11 +93,10 @@ public: /** When true the animations are played looped */ virtual bool getLoopMode() const = 0; - //! Sets a callback interface which will be called if an animation playback has ended. - /** Set this to 0 to disable the callback again. - Please note that this will only be called when in non looped - mode, see IAnimatedMeshSceneNode::setLoopMode(). */ - virtual void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) = 0; + //! Will be called right after the joints have been animated, + //! but before the transforms have been propagated recursively to children. + virtual void setOnAnimateCallback( + const std::function &cb) = 0; //! 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 @@ -139,20 +111,15 @@ public: virtual void setMesh(IAnimatedMesh *mesh) = 0; //! Returns the current mesh - virtual IAnimatedMesh *getMesh(void) = 0; - - //! Set how the joints should be updated on render - virtual void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) = 0; + virtual IAnimatedMesh *getMesh() = 0; //! Sets the transition time in seconds - /** Note: This needs to enable joints, and setJointmode set to - EJUOR_CONTROL. You must call animateJoints(), or the mesh will - not animate. */ + /** Note: You must call animateJoints(), or the mesh will not animate. */ virtual void setTransitionTime(f32 Time) = 0; //! animates the joints in the mesh based on the current frame. /** Also takes in to account transitions. */ - virtual void animateJoints(bool CalculateAbsolutePositions = true) = 0; + virtual void animateJoints() = 0; //! render mesh ignoring its transformation. /** Culling is unaffected. */ diff --git a/irr/include/IBoneSceneNode.h b/irr/include/IBoneSceneNode.h index eef55f6e0..d169b8d30 100644 --- a/irr/include/IBoneSceneNode.h +++ b/irr/include/IBoneSceneNode.h @@ -11,85 +11,41 @@ namespace irr namespace scene { -//! Enumeration for different bone animation modes -enum E_BONE_ANIMATION_MODE -{ - //! The bone is usually animated, unless it's parent is not animated - EBAM_AUTOMATIC = 0, - - //! The bone is animated by the skin, if it's parent is not animated then animation will resume from this bone onward - EBAM_ANIMATED, - - //! The bone is not animated by the skin - EBAM_UNANIMATED, - - //! Not an animation mode, just here to count the available modes - EBAM_COUNT - -}; - -enum E_BONE_SKINNING_SPACE -{ - //! local skinning, standard - EBSS_LOCAL = 0, - - //! global skinning - EBSS_GLOBAL, - - EBSS_COUNT -}; - -//! Names for bone animation modes -const c8 *const BoneAnimationModeNames[] = { - "automatic", - "animated", - "unanimated", - 0, - }; - //! Interface for bones used for skeletal animation. /** Used with SkinnedMesh and IAnimatedMeshSceneNode. */ class IBoneSceneNode : public ISceneNode { public: - IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1) : - ISceneNode(parent, mgr, id), positionHint(-1), scaleHint(-1), rotationHint(-1) {} + IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, + s32 id = -1, u32 boneIndex = 0, + const std::optional &boneName = std::nullopt) + : + ISceneNode(parent, mgr, id), + BoneIndex(boneIndex) + { + setName(boneName); + } - //! Get the index of the bone - virtual u32 getBoneIndex() const = 0; + //! Returns the index of the bone + u32 getBoneIndex() const + { + return BoneIndex; + } - //! Sets the animation mode of the bone. - /** \return True if successful. (Unused) */ - virtual bool setAnimationMode(E_BONE_ANIMATION_MODE mode) = 0; + //! returns the axis aligned bounding box of this node + const core::aabbox3d &getBoundingBox() const override + { + return Box; + } - //! Gets the current animation mode of the bone - virtual E_BONE_ANIMATION_MODE getAnimationMode() const = 0; + const u32 BoneIndex; - //! Get the axis aligned bounding box of this node - const core::aabbox3d &getBoundingBox() const override = 0; - - //! Returns the relative transformation of the scene node. - // virtual core::matrix4 getRelativeTransformation() const = 0; - - //! The animation method. - void OnAnimate(u32 timeMs) override = 0; + // Bogus box; bone scene nodes are not rendered anyways. + static constexpr core::aabbox3d Box = {{0, 0, 0}}; //! The render method. /** Does nothing as bones are not visible. */ void render() override {} - - //! How the relative transformation of the bone is used - virtual void setSkinningSpace(E_BONE_SKINNING_SPACE space) = 0; - - //! How the relative transformation of the bone is used - virtual E_BONE_SKINNING_SPACE getSkinningSpace() const = 0; - - //! Updates the absolute position based on the relative and the parents position - virtual void updateAbsolutePositionOfAllChildren() = 0; - - s32 positionHint; - s32 scaleHint; - s32 rotationHint; }; } // end namespace scene diff --git a/irr/include/IMesh.h b/irr/include/IMesh.h index 8ee180d5d..e8656e868 100644 --- a/irr/include/IMesh.h +++ b/irr/include/IMesh.h @@ -20,38 +20,6 @@ enum E_ANIMATED_MESH_TYPE //! Unknown animated mesh type. EAMT_UNKNOWN = 0, - //! Quake 2 MD2 model file - EAMT_MD2, - - //! Quake 3 MD3 model file - EAMT_MD3, - - //! Maya .obj static model - EAMT_OBJ, - - //! Quake 3 .bsp static Map - EAMT_BSP, - - //! 3D Studio .3ds file - EAMT_3DS, - - //! My3D Mesh, the file format by Zhuck Dimitry - EAMT_MY3D, - - //! Pulsar LMTools .lmts file. This Irrlicht loader was written by Jonas Petersen - EAMT_LMTS, - - //! Cartography Shop .csm file. This loader was created by Saurav Mohapatra. - EAMT_CSM, - - //! .oct file for Paul Nette's FSRad or from Murphy McCauley's Blender .oct exporter. - /** The oct file format contains 3D geometry and lightmaps and - can be loaded directly by Irrlicht */ - EAMT_OCT, - - //! Halflife MDL model file - EAMT_MDL_HALFLIFE, - //! generic skinned mesh EAMT_SKINNED, @@ -119,9 +87,7 @@ public: virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0; //! Returns the type of the meshes. - /** This is useful for making a safe downcast. For example, - if getMeshType() returns EAMT_MD2 it's safe to cast the - IMesh to IAnimatedMeshMD2. + /** This is useful for making a safe downcast. Note: It's no longer just about animated meshes, that name has just historical reasons. \returns Type of the mesh */ virtual E_ANIMATED_MESH_TYPE getMeshType() const diff --git a/irr/include/IMeshManipulator.h b/irr/include/IMeshManipulator.h index c9d989cae..0312a38c8 100644 --- a/irr/include/IMeshManipulator.h +++ b/irr/include/IMeshManipulator.h @@ -66,26 +66,6 @@ public: IReferenceCounted::drop() for more information. */ virtual SMesh *createMeshCopy(IMesh *mesh) const = 0; - //! Get amount of polygons in mesh. - /** \param mesh Input mesh - \return Number of polygons in mesh. */ - virtual s32 getPolyCount(IMesh *mesh) const = 0; - - //! Get amount of polygons in mesh. - /** \param mesh Input mesh - \return Number of polygons in mesh. */ - virtual s32 getPolyCount(IAnimatedMesh *mesh) const = 0; - - //! Create a new AnimatedMesh and adds the mesh to it - /** \param mesh Input mesh - \param type The type of the animated mesh to create. - \return Newly created animated mesh with mesh as its only - content. When you don't need the animated mesh anymore, you - should call IAnimatedMesh::drop(). See - IReferenceCounted::drop() for more information. */ - virtual IAnimatedMesh *createAnimatedMesh(IMesh *mesh, - scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) const = 0; - //! Apply a manipulator on the Meshbuffer /** \param func A functor defining the mesh manipulation. \param buffer The Meshbuffer to apply the manipulator to. diff --git a/irr/include/IMeshSceneNode.h b/irr/include/IMeshSceneNode.h index 1fb005405..3065f4c07 100644 --- a/irr/include/IMeshSceneNode.h +++ b/irr/include/IMeshSceneNode.h @@ -32,7 +32,7 @@ public: //! Get the currently defined mesh for display. /** \return Pointer to mesh which is displayed by this node. */ - virtual IMesh *getMesh(void) = 0; + virtual IMesh *getMesh() = 0; //! Sets if the scene node should not copy the materials of the mesh but use them directly. /** In this way it is possible to change the materials of a mesh diff --git a/irr/include/ISceneNode.h b/irr/include/ISceneNode.h index f91fd6499..573715861 100644 --- a/irr/include/ISceneNode.h +++ b/irr/include/ISceneNode.h @@ -94,16 +94,12 @@ public: \param timeMs Current time in milliseconds. */ virtual void OnAnimate(u32 timeMs) { - if (IsVisible) { - // update absolute position - updateAbsolutePosition(); + if (!IsVisible && Children.empty()) + return; - // perform the post render process on all children - - ISceneNodeList::iterator it = Children.begin(); - for (; it != Children.end(); ++it) - (*it)->OnAnimate(timeMs); - } + updateAbsolutePosition(); + for (auto *child : Children) + child->OnAnimate(timeMs); } //! Renders the node. diff --git a/irr/include/SAnimatedMesh.h b/irr/include/SAnimatedMesh.h deleted file mode 100644 index dcc65410f..000000000 --- a/irr/include/SAnimatedMesh.h +++ /dev/null @@ -1,167 +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 -#include "IAnimatedMesh.h" -#include "IMesh.h" -#include "aabbox3d.h" - -namespace irr -{ -namespace scene -{ - -//! Simple implementation of the IAnimatedMesh interface. -struct SAnimatedMesh final : public IAnimatedMesh -{ - //! constructor - SAnimatedMesh(scene::IMesh *mesh = 0, scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) : - IAnimatedMesh(), FramesPerSecond(25.f), Type(type) - { - addMesh(mesh); - recalculateBoundingBox(); - } - - //! destructor - virtual ~SAnimatedMesh() - { - // drop meshes - for (auto *mesh : Meshes) - mesh->drop(); - } - - f32 getMaxFrameNumber() const override - { - return static_cast(Meshes.size() - 1); - } - - //! 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. */ - f32 getAnimationSpeed() const override - { - return FramesPerSecond; - } - - //! Gets the frame count of the animated mesh. - /** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated. - The actual speed is set in the scene node the mesh is instantiated in.*/ - void setAnimationSpeed(f32 fps) override - { - FramesPerSecond = fps; - } - - //! Returns the IMesh interface for a frame. - /** \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 nullptr; - - return Meshes[static_cast(frame)]; - } - - //! adds a Mesh - void addMesh(IMesh *mesh) - { - if (mesh) { - mesh->grab(); - Meshes.push_back(mesh); - } - } - - //! Returns an axis aligned bounding box of the mesh. - /** \return A bounding box of this mesh is returned. */ - const core::aabbox3d &getBoundingBox() const override - { - return Box; - } - - //! set user axis aligned bounding box - void setBoundingBox(const core::aabbox3df &box) override - { - Box = box; - } - - //! Recalculates the bounding box. - void recalculateBoundingBox() - { - Box.reset(0, 0, 0); - - if (Meshes.empty()) - return; - - Box = Meshes[0]->getBoundingBox(); - - for (u32 i = 1; i < Meshes.size(); ++i) - Box.addInternalBox(Meshes[i]->getBoundingBox()); - } - - //! Returns the type of the animated mesh. - E_ANIMATED_MESH_TYPE getMeshType() const override - { - return Type; - } - - //! returns amount of mesh buffers. - u32 getMeshBufferCount() const override - { - if (Meshes.empty()) - return 0; - - return Meshes[0]->getMeshBufferCount(); - } - - //! returns pointer to a mesh buffer - IMeshBuffer *getMeshBuffer(u32 nr) const override - { - if (Meshes.empty()) - return 0; - - return Meshes[0]->getMeshBuffer(nr); - } - - //! Returns pointer to a mesh buffer which fits a material - /** \param material: material to search for - \return Returns the pointer to the mesh buffer or - NULL if there is no such mesh buffer. */ - IMeshBuffer *getMeshBuffer(const video::SMaterial &material) const override - { - if (Meshes.empty()) - return 0; - - return Meshes[0]->getMeshBuffer(material); - } - - //! 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 < Meshes.size(); ++i) - Meshes[i]->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 < Meshes.size(); ++i) - Meshes[i]->setDirty(buffer); - } - - //! All meshes defining the animated mesh - std::vector Meshes; - - //! The bounding box of this mesh - core::aabbox3d Box{{0.0f, 0.0f, 0.0f}}; - - //! Default animation speed of this mesh. - f32 FramesPerSecond; - - //! The type of the mesh. - E_ANIMATED_MESH_TYPE Type; -}; - -} // end namespace scene -} // end namespace irr diff --git a/irr/include/SMesh.h b/irr/include/SMesh.h index 5e76fafc5..b22bb7749 100644 --- a/irr/include/SMesh.h +++ b/irr/include/SMesh.h @@ -5,7 +5,7 @@ #pragma once #include -#include "IMesh.h" +#include "IAnimatedMesh.h" #include "IMeshBuffer.h" #include "aabbox3d.h" @@ -14,7 +14,7 @@ namespace irr namespace scene { //! Simple implementation of the IMesh interface. -struct SMesh final : public IMesh +struct SMesh final : public IAnimatedMesh { //! constructor SMesh() {} @@ -134,6 +134,15 @@ struct SMesh final : public IMesh //! The bounding box of this mesh core::aabbox3d BoundingBox{{0, 0, 0}}; + + // Implement animated mesh interface as a static mesh. + // Slightly hacky: Eventually should be consolidated with SSkinnedMesh, + // with all the animation-related parts behind an optional. + + virtual f32 getMaxFrameNumber() const override { return 0.0f; } + virtual f32 getAnimationSpeed() const override { return 0.0f; } + virtual void setAnimationSpeed(f32 fps) override {} + E_ANIMATED_MESH_TYPE getMeshType() const override { return EAMT_STATIC; } }; } // end namespace scene diff --git a/irr/include/SkinnedMesh.h b/irr/include/SkinnedMesh.h index a527db76e..aa718c882 100644 --- a/irr/include/SkinnedMesh.h +++ b/irr/include/SkinnedMesh.h @@ -8,11 +8,18 @@ #include "ISceneManager.h" #include "SMeshBuffer.h" #include "SSkinMeshBuffer.h" +#include "aabbox3d.h" +#include "irrMath.h" +#include "irrTypes.h" +#include "matrix4.h" #include "quaternion.h" #include "vector3d.h" +#include "Transform.h" #include #include +#include +#include namespace irr { @@ -37,9 +44,8 @@ public: //! constructor SkinnedMesh(SourceFormat src_format) : EndFrame(0.f), FramesPerSecond(25.f), - LastAnimatedFrame(-1), SkinnedLastFrame(false), HasAnimation(false), PreparedForSkinning(false), - AnimateNormals(true), HardwareSkinning(false), + AnimateNormals(true), SrcFormat(src_format) { SkinningBuffers = &LocalBuffers; @@ -64,14 +70,12 @@ public: The actual speed is set in the scene node the mesh is instantiated in.*/ void setAnimationSpeed(f32 fps) override; - //! returns the animated mesh for the given frame - IMesh *getMesh(f32) override; + //! Turns the given array of local matrices into an array of global matrices + //! by multiplying with respective parent matrices. + void calculateGlobalMatrices(std::vector &matrices) const; - //! Animates joints based on frame input - void animateMesh(f32 frame); - - //! Performs a software skin on this mesh based of joint positions - void skinMesh(); + //! Performs a software skin on this mesh based on the given joint matrices + void skinMesh(const std::vector &animated_transforms); //! returns amount of mesh buffers. u32 getMeshBufferCount() const override; @@ -89,14 +93,15 @@ public: void setTextureSlot(u32 meshbufNr, u32 textureSlot); - //! returns an axis aligned bounding box + //! Returns bounding box of the mesh *in static pose*. const core::aabbox3d &getBoundingBox() const override { - return BoundingBox; + // TODO ideally we shouldn't be forced to implement this + return StaticPoseBox; } - //! set user axis aligned bounding box + //! Set bounding box of the mesh *in static pose*. void setBoundingBox(const core::aabbox3df &box) override { - BoundingBox = box; + StaticPoseBox = box; } //! set the hardware mapping hint, for driver @@ -140,28 +145,15 @@ public: return !HasAnimation; } - //! Allows to enable hardware skinning. - /* This feature is not implemented in Irrlicht yet */ - bool setHardwareSkinning(bool on); - //! Refreshes vertex data cached in joints such as positions and normals void refreshJointCache(); //! Moves the mesh into static position. void resetAnimation(); - void updateBoundingBox(); - - //! Recovers the joints from the mesh - void recoverJointsFromMesh(std::vector &jointChildSceneNodes); - - //! Transfers the joint data to the mesh - void transferJointsToMesh(const std::vector &jointChildSceneNodes); - //! Creates an array of joints from this mesh as children of node - void addJoints(std::vector &jointChildSceneNodes, - IAnimatedMeshSceneNode *node, - ISceneManager *smgr); + std::vector addJoints( + IAnimatedMeshSceneNode *node, ISceneManager *smgr); //! A vertex weight struct SWeight @@ -236,7 +228,7 @@ public: static core::quaternion interpolateValue(core::quaternion from, core::quaternion to, f32 time) { core::quaternion result; - result.slerp(from, to, time, 0.001f); + result.slerp(from, to, time); return result; } @@ -288,15 +280,14 @@ public: }); } - void updateTransform(f32 frame, - core::vector3df &t, core::quaternion &r, core::vector3df &s) const + void updateTransform(f32 frame, core::Transform &transform) const { if (auto pos = position.get(frame)) - t = *pos; + transform.translation = *pos; if (auto rot = rotation.get(frame)) - r = *rot; + transform.rotation = *rot; if (auto scl = scale.get(frame)) - s = *scl; + transform.scale = *scl; } void cleanup() { @@ -309,16 +300,34 @@ public: //! Joints struct SJoint { - SJoint() : GlobalSkinningSpace(false) {} + SJoint() {} //! The name of this joint std::optional Name; - //! Local matrix of this joint - core::matrix4 LocalMatrix; + //! Local transformation to be set by loaders. Mutated by animation. + using VariantTransform = std::variant; + VariantTransform transform{core::Transform{}}; + + VariantTransform animate(f32 frame) const { + if (keys.empty()) + return transform; + + if (std::holds_alternative(transform)) { + // .x lets animations override matrix transforms entirely, + // which is what we implement here. + // .gltf does not allow animation of nodes using matrix transforms. + // Note that a decomposition into a TRS transform need not exist! + core::Transform trs; + keys.updateTransform(frame, trs); + return {trs}; + } + + auto trs = std::get(transform); + keys.updateTransform(frame, trs); + return {trs}; + } - //! List of child joints - std::vector Children; //! List of attached meshes std::vector AttachedMeshes; @@ -329,42 +338,49 @@ public: //! Skin weights std::vector Weights; + //! Bounding box of all affected vertices, in local space + core::aabbox3df LocalBoundingBox{{0, 0, 0}}; + //! Unnecessary for loaders, will be overwritten on finalize 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; // The .x and .gltf formats pre-calculate this std::optional GlobalInversedMatrix; - private: - //! Internal members used by SkinnedMesh - friend class SkinnedMesh; - bool GlobalSkinningSpace; + void setParent(SJoint *parent) { + ParentJointID = parent ? parent->JointID : std::optional{}; + } + + u16 JointID; // TODO refactor away: pointers -> IDs (problem: .x loader abuses SJoint) + std::optional ParentJointID; }; + //! Animates joints based on frame input + std::vector animateMesh(f32 frame); + + //! Calculates a bounding box given an animation in the form of global joint transforms. + core::aabbox3df calculateBoundingBox( + const std::vector &global_transforms); + + void recalculateBaseBoundingBoxes(); + const std::vector &getAllJoints() const { return AllJoints; } protected: - void checkForAnimation(); + bool checkForAnimation() const; + + void topoSortJoints(); + + void prepareForSkinning(); + + void calculateStaticBoundingBox(); + void calculateJointBoundingBoxes(); + void calculateBufferBoundingBoxes(); void normalizeWeights(); - void buildAllLocalAnimatedMatrices(); - - void buildAllGlobalAnimatedMatrices(SJoint *Joint = 0, SJoint *ParentJoint = 0); - - void calculateGlobalMatrices(SJoint *Joint, SJoint *ParentJoint); - - void skinJoint(SJoint *Joint, SJoint *ParentJoint); - void calculateTangents(core::vector3df &normal, core::vector3df &tangent, core::vector3df &binormal, const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3, @@ -376,25 +392,25 @@ protected: //! Mapping from meshbuffer number to bindable texture slot std::vector TextureSlots; + //! Joints, topologically sorted (parents come before their children). std::vector AllJoints; - std::vector RootJoints; // bool can't be used here because std::vector // doesn't allow taking a reference to individual elements. std::vector> Vertices_Moved; - core::aabbox3d BoundingBox{{0, 0, 0}}; + //! Bounding box of just the static parts of the mesh + core::aabbox3df StaticPartsBox{{0, 0, 0}}; + + //! Bounding box of the mesh in static pose + core::aabbox3df StaticPoseBox{{0, 0, 0}}; f32 EndFrame; f32 FramesPerSecond; - f32 LastAnimatedFrame; - bool SkinnedLastFrame; - bool HasAnimation; bool PreparedForSkinning; bool AnimateNormals; - bool HardwareSkinning; SourceFormat SrcFormat; }; diff --git a/irr/include/Transform.h b/irr/include/Transform.h new file mode 100644 index 000000000..1e96e183d --- /dev/null +++ b/irr/include/Transform.h @@ -0,0 +1,42 @@ +#pragma once + +#include "irrMath.h" +#include +#include +#include + +namespace irr +{ +namespace core +{ + +struct Transform { + vector3df translation; + quaternion rotation; + vector3df scale{1}; + + Transform interpolate(Transform to, f32 time) const + { + core::quaternion interpolated_rotation; + interpolated_rotation.slerp(rotation, to.rotation, time); + return { + to.translation.getInterpolated(translation, time), + interpolated_rotation, + to.scale.getInterpolated(scale, time), + }; + } + + matrix4 buildMatrix() const + { + matrix4 T; + T.setTranslation(translation); + matrix4 R; + rotation.getMatrix_transposed(R); + matrix4 S; + S.setScale(scale); + return T * R * S; + } +}; + +} // end namespace core +} // end namespace irr diff --git a/irr/include/quaternion.h b/irr/include/quaternion.h index e23b1317d..42e0428a9 100644 --- a/irr/include/quaternion.h +++ b/irr/include/quaternion.h @@ -180,7 +180,7 @@ public: linear interpolation. */ quaternion &slerp(quaternion q1, quaternion q2, - f32 time, f32 threshold = .05f); + f32 time, f32 threshold = .001f); //! Set this quaternion to represent a rotation from angle and axis. /** Axis must be unit length. diff --git a/irr/src/CAnimatedMeshSceneNode.cpp b/irr/src/CAnimatedMeshSceneNode.cpp index 09d83038b..7b1d9053a 100644 --- a/irr/src/CAnimatedMeshSceneNode.cpp +++ b/irr/src/CAnimatedMeshSceneNode.cpp @@ -3,9 +3,13 @@ // For conditions of distribution and use, see copyright notice in irrlicht.h #include "CAnimatedMeshSceneNode.h" +#include "CBoneSceneNode.h" #include "IVideoDriver.h" #include "ISceneManager.h" #include "S3DVertex.h" +#include "Transform.h" +#include "irrTypes.h" +#include "matrix4.h" #include "os.h" #include "SkinnedMesh.h" #include "IDummyTransformationSceneNode.h" @@ -17,6 +21,9 @@ #include "IFileSystem.h" #include "quaternion.h" #include +#include +#include +#include namespace irr { @@ -30,13 +37,13 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh, const core::vector3df &rotation, const core::vector3df &scale) : IAnimatedMeshSceneNode(parent, mgr, id, position, rotation, scale), - Mesh(0), + Mesh(nullptr), StartFrame(0), EndFrame(0), FramesPerSecond(0.025f), CurrentFrameNr(0.f), LastTimeMs(0), TransitionTime(0), Transiting(0.f), TransitingBlend(0.f), - JointMode(EJUOR_NONE), JointsUsed(false), + JointsUsed(false), Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false), - LoopCallBack(0), PassCount(0) + PassCount(0) { setMesh(mesh); } @@ -44,8 +51,6 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh, //! destructor CAnimatedMeshSceneNode::~CAnimatedMeshSceneNode() { - if (LoopCallBack) - LoopCallBack->drop(); if (Mesh) Mesh->drop(); } @@ -87,8 +92,7 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs) if (FramesPerSecond > 0.f) { // forwards... if (CurrentFrameNr > EndFrame) CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame); - } else // backwards... - { + } else { // backwards... if (CurrentFrameNr < StartFrame) CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame); } @@ -97,18 +101,9 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs) CurrentFrameNr += timeMs * FramesPerSecond; if (FramesPerSecond > 0.f) { // forwards... - if (CurrentFrameNr > EndFrame) { - CurrentFrameNr = EndFrame; - if (LoopCallBack) - LoopCallBack->OnAnimationEnd(this); - } - } else // backwards... - { - if (CurrentFrameNr < StartFrame) { - CurrentFrameNr = StartFrame; - if (LoopCallBack) - LoopCallBack->OnAnimationEnd(this); - } + CurrentFrameNr = std::min(CurrentFrameNr, EndFrame); + } else { // backwards... + CurrentFrameNr = std::max(CurrentFrameNr, StartFrame); } } } @@ -156,38 +151,18 @@ void CAnimatedMeshSceneNode::OnRegisterSceneNode() IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame() { if (Mesh->getMeshType() != EAMT_SKINNED) { - 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. - - SkinnedMesh *skinnedMesh = static_cast(Mesh); - - if (JointMode == EJUOR_CONTROL) // write to mesh - skinnedMesh->transferJointsToMesh(JointChildSceneNodes); - else - skinnedMesh->animateMesh(getFrameNr()); - - // Update the skinned mesh for the current joint transforms. - skinnedMesh->skinMesh(); - - if (JointMode == EJUOR_READ) { // read from mesh - skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes); - - //---slow--- - for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) - if (JointChildSceneNodes[n]->getParent() == this) { - JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option - } - } - - if (JointMode == EJUOR_CONTROL) { - // For meshes other than EJUOR_CONTROL, this is done by calling animateMesh() - skinnedMesh->updateBoundingBox(); - } - - return skinnedMesh; + return Mesh; } + + // 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. + + auto *skinnedMesh = static_cast(Mesh); + + // Matrices have already been calculated in OnAnimate + skinnedMesh->skinMesh(PerJoint.GlobalMatrices); + + return skinnedMesh; } //! OnAnimate() is called just before rendering the whole scene. @@ -201,7 +176,28 @@ void CAnimatedMeshSceneNode::OnAnimate(u32 timeMs) buildFrameNr(timeMs - LastTimeMs); LastTimeMs = timeMs; + // This needs to be done on animate, which is called recursively *before* + // anything is rendered so that the transformations of children are up to date + animateJoints(); + + // Copy old transforms *before* bone overrides have been applied. + // TODO if there are no bone overrides or no animation blending, this is unnecessary. + copyOldTransforms(); + + if (OnAnimateCallback) + OnAnimateCallback(timeMs / 1000.0f); + IAnimatedMeshSceneNode::OnAnimate(timeMs); + + if (auto *skinnedMesh = dynamic_cast(Mesh)) { + for (u16 i = 0; i < PerJoint.SceneNodes.size(); ++i) + PerJoint.GlobalMatrices[i] = PerJoint.SceneNodes[i]->getRelativeTransformation(); + assert(PerJoint.GlobalMatrices.size() == skinnedMesh->getJointCount()); + skinnedMesh->calculateGlobalMatrices(PerJoint.GlobalMatrices); + Box = skinnedMesh->calculateBoundingBox(PerJoint.GlobalMatrices); + } else { + Box = Mesh->getBoundingBox(); + } } //! renders the node. @@ -218,15 +214,7 @@ void CAnimatedMeshSceneNode::render() ++PassCount; scene::IMesh *m = getMeshForCurrentFrame(); - - if (m) { - Box = m->getBoundingBox(); - } else { -#ifdef _DEBUG - os::Printer::log("Animated Mesh returned no mesh to render.", ELL_WARNING); -#endif - return; - } + assert(m); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); @@ -294,11 +282,12 @@ void CAnimatedMeshSceneNode::render() if (DebugDataVisible & scene::EDS_SKELETON) { if (Mesh->getMeshType() == EAMT_SKINNED) { // draw skeleton - - for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) { - for (const auto *childJoint : joint->Children) { - driver->draw3DLine(joint->GlobalAnimatedMatrix.getTranslation(), - childJoint->GlobalAnimatedMatrix.getTranslation(), + const auto &joints = (static_cast(Mesh))->getAllJoints(); + for (u16 i = 0; i < PerJoint.GlobalMatrices.size(); ++i) { + const auto translation = PerJoint.GlobalMatrices[i].getTranslation(); + if (auto pjid = joints[i]->ParentJointID) { + const auto parent_translation = PerJoint.GlobalMatrices[*pjid].getTranslation(); + driver->draw3DLine(parent_translation, translation, video::SColor(255, 51, 66, 255)); } } @@ -407,12 +396,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(const c8 *jointName) return 0; } - if (JointChildSceneNodes.size() <= *number) { + if (PerJoint.SceneNodes.size() <= *number) { os::Printer::log("Joint was found in mesh, but is not loaded into node", jointName, ELL_WARNING); return 0; } - return JointChildSceneNodes[*number]; + return PerJoint.SceneNodes[*number]; } //! Returns a pointer to a child node, which has the same transformation as @@ -426,12 +415,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(u32 jointID) checkJoints(); - if (JointChildSceneNodes.size() <= jointID) { + if (PerJoint.SceneNodes.size() <= jointID) { os::Printer::log("Joint not loaded into node", ELL_WARNING); return 0; } - return JointChildSceneNodes[jointID]; + return PerJoint.SceneNodes[jointID]; } //! Gets joint count. @@ -452,9 +441,9 @@ bool CAnimatedMeshSceneNode::removeChild(ISceneNode *child) { if (ISceneNode::removeChild(child)) { if (JointsUsed) { // stop weird bugs caused while changing parents as the joints are being created - for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) { - if (JointChildSceneNodes[i] == child) { - JointChildSceneNodes[i] = 0; // remove link to child + for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) { + if (PerJoint.SceneNodes[i] == child) { + PerJoint.SceneNodes[i] = 0; // remove link to child break; } } @@ -478,22 +467,6 @@ bool CAnimatedMeshSceneNode::getLoopMode() const return Looping; } -//! Sets a callback interface which will be called if an animation -//! playback has ended. Set this to 0 to disable the callback again. -void CAnimatedMeshSceneNode::setAnimationEndCallback(IAnimationEndCallBack *callback) -{ - if (callback == LoopCallBack) - return; - - if (LoopCallBack) - LoopCallBack->drop(); - - LoopCallBack = callback; - - if (LoopCallBack) - LoopCallBack->grab(); -} - //! Sets if the scene node should not copy the materials of the mesh but use them in a read only style. void CAnimatedMeshSceneNode::setReadOnlyMaterials(bool readonly) { @@ -525,18 +498,15 @@ void CAnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh) // get materials and bounding box Box = Mesh->getBoundingBox(); - IMesh *m = Mesh->getMesh(0); - if (m) { - Materials.clear(); - Materials.reallocate(m->getMeshBufferCount()); + Materials.clear(); + Materials.reallocate(Mesh->getMeshBufferCount()); - for (u32 i = 0; i < m->getMeshBufferCount(); ++i) { - IMeshBuffer *mb = m->getMeshBuffer(i); - if (mb) - Materials.push_back(mb->getMaterial()); - else - Materials.push_back(video::SMaterial()); - } + for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i) { + IMeshBuffer *mb = Mesh->getMeshBuffer(i); + if (mb) + Materials.push_back(mb->getMaterial()); + else + Materials.push_back(video::SMaterial()); } // clean up joint nodes @@ -556,14 +526,7 @@ void CAnimatedMeshSceneNode::updateAbsolutePosition() IAnimatedMeshSceneNode::updateAbsolutePosition(); } -//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set) -void CAnimatedMeshSceneNode::setJointMode(E_JOINT_UPDATE_ON_RENDER mode) -{ - checkJoints(); - JointMode = mode; -} - -//! Sets the transition time in seconds (note: This needs to enable joints, and setJointmode maybe set to 2) +//! Sets the transition time in seconds (note: This needs to enable joints) //! you must call animateJoints(), or the mesh will not animate void CAnimatedMeshSceneNode::setTransitionTime(f32 time) { @@ -571,10 +534,6 @@ void CAnimatedMeshSceneNode::setTransitionTime(f32 time) if (TransitionTime == ttime) return; TransitionTime = ttime; - if (ttime != 0) - setJointMode(EJUOR_CONTROL); - else - setJointMode(EJUOR_NONE); } //! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected) @@ -583,120 +542,104 @@ void CAnimatedMeshSceneNode::setRenderFromIdentity(bool enable) RenderFromIdentity = enable; } -//! updates the joint positions of this mesh -void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions) +void CAnimatedMeshSceneNode::addJoints() { - if (Mesh && Mesh->getMeshType() == EAMT_SKINNED) { - checkJoints(); - const f32 frame = getFrameNr(); // old? + const auto &joints = static_cast(Mesh)->getAllJoints(); + PerJoint.setN(joints.size()); + PerJoint.SceneNodes.clear(); + PerJoint.SceneNodes.reserve(joints.size()); + for (size_t i = 0; i < joints.size(); ++i) { + const auto *joint = joints[i]; + ISceneNode *parent = this; + if (joint->ParentJointID) + parent = PerJoint.SceneNodes.at(*joint->ParentJointID); // exists because of topo. order + assert(parent); + const auto *matrix = std::get_if(&joint->transform); + PerJoint.SceneNodes.push_back(new CBoneSceneNode( + parent, SceneManager, 0, i, joint->Name, + matrix ? core::Transform{} : std::get(joint->transform), + matrix ? *matrix : std::optional{})); + } +} - SkinnedMesh *skinnedMesh = static_cast(Mesh); - - skinnedMesh->animateMesh(frame); - skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes); - - //----------------------------------------- - // Transition - //----------------------------------------- - - if (Transiting != 0.f) { - // Init additional matrices - if (PretransitingSave.size() < JointChildSceneNodes.size()) { - for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n) - PretransitingSave.push_back(core::matrix4()); - } - - for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) { - //------Position------ - - JointChildSceneNodes[n]->setPosition( - core::lerp( - PretransitingSave[n].getTranslation(), - JointChildSceneNodes[n]->getPosition(), - TransitingBlend)); - - //------Rotation------ - - // Code is slow, needs to be fixed up - - const core::quaternion RotationStart(PretransitingSave[n].getRotationRadians()); - const core::quaternion RotationEnd(JointChildSceneNodes[n]->getRotation() * core::DEGTORAD); - - core::quaternion QRotation; - QRotation.slerp(RotationStart, RotationEnd, TransitingBlend); - - core::vector3df tmpVector; - QRotation.toEuler(tmpVector); - tmpVector *= core::RADTODEG; // convert from radians back to degrees - JointChildSceneNodes[n]->setRotation(tmpVector); - - //------Scale------ - - // JointChildSceneNodes[n]->setScale( - // core::lerp( - // PretransitingSave[n].getScale(), - // JointChildSceneNodes[n]->getScale(), - // TransitingBlend)); - } +void CAnimatedMeshSceneNode::updateJointSceneNodes( + const std::vector &transforms) +{ + for (size_t i = 0; i < transforms.size(); ++i) { + const auto &transform = transforms[i]; + auto *node = static_cast(PerJoint.SceneNodes[i]); + if (const auto *trs = std::get_if(&transform)) { + node->setTransform(*trs); + // .x lets animations override matrix transforms entirely. + node->Matrix = std::nullopt; + } else { + node->Matrix = std::get(transform); } + } +} - if (CalculateAbsolutePositions) { - //---slow--- - for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) { - if (JointChildSceneNodes[n]->getParent() == this) { - JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option - } +//! updates the joint positions of this mesh +void CAnimatedMeshSceneNode::animateJoints() +{ + if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED) + return; + + checkJoints(); + + SkinnedMesh *skinnedMesh = static_cast(Mesh); + if (!skinnedMesh->isStatic()) + updateJointSceneNodes(skinnedMesh->animateMesh(getFrameNr())); + + //----------------------------------------- + // Transition + //----------------------------------------- + + if (Transiting != 0.f) { + for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) { + if (PerJoint.PreTransSaves[i]) { + PerJoint.SceneNodes[i]->setTransform(PerJoint.PreTransSaves[i]->interpolate( + PerJoint.SceneNodes[i]->getTransform(), TransitingBlend)); } } } } -/*! - */ void CAnimatedMeshSceneNode::checkJoints() { if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED) return; if (!JointsUsed) { - for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) - removeChild(JointChildSceneNodes[i]); - JointChildSceneNodes.clear(); - - // Create joints for SkinnedMesh - ((SkinnedMesh *)Mesh)->addJoints(JointChildSceneNodes, this, SceneManager); - ((SkinnedMesh *)Mesh)->recoverJointsFromMesh(JointChildSceneNodes); + for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) + removeChild(PerJoint.SceneNodes[i]); + addJoints(); JointsUsed = true; - JointMode = EJUOR_READ; } } -/*! - */ +void CAnimatedMeshSceneNode::copyOldTransforms() +{ + for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) { + if (!PerJoint.SceneNodes[i]->Matrix) { + PerJoint.PreTransSaves[i] = PerJoint.SceneNodes[i]->getTransform(); + } else { + PerJoint.PreTransSaves[i] = std::nullopt; + } + } +} + void CAnimatedMeshSceneNode::beginTransition() { if (!JointsUsed) return; if (TransitionTime != 0) { - // Check the array is big enough - if (PretransitingSave.size() < JointChildSceneNodes.size()) { - for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n) - PretransitingSave.push_back(core::matrix4()); - } - - // Copy the position of joints - for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) - PretransitingSave[n] = JointChildSceneNodes[n]->getRelativeTransformation(); - Transiting = core::reciprocal((f32)TransitionTime); } TransitingBlend = 0.f; } -/*! - */ ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManager) { if (!newParent) @@ -722,19 +665,15 @@ ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager * newNode->EndFrame = EndFrame; newNode->FramesPerSecond = FramesPerSecond; newNode->CurrentFrameNr = CurrentFrameNr; - newNode->JointMode = JointMode; newNode->JointsUsed = JointsUsed; newNode->TransitionTime = TransitionTime; newNode->Transiting = Transiting; newNode->TransitingBlend = TransitingBlend; newNode->Looping = Looping; newNode->ReadOnlyMaterials = ReadOnlyMaterials; - newNode->LoopCallBack = LoopCallBack; - if (newNode->LoopCallBack) - newNode->LoopCallBack->grab(); newNode->PassCount = PassCount; - newNode->JointChildSceneNodes = JointChildSceneNodes; - newNode->PretransitingSave = PretransitingSave; + newNode->PerJoint.SceneNodes = PerJoint.SceneNodes; + newNode->PerJoint.PreTransSaves = PerJoint.PreTransSaves; newNode->RenderFromIdentity = RenderFromIdentity; return newNode; diff --git a/irr/src/CAnimatedMeshSceneNode.h b/irr/src/CAnimatedMeshSceneNode.h index 5149f7618..c67479481 100644 --- a/irr/src/CAnimatedMeshSceneNode.h +++ b/irr/src/CAnimatedMeshSceneNode.h @@ -4,9 +4,12 @@ #pragma once +#include "CBoneSceneNode.h" #include "IAnimatedMeshSceneNode.h" #include "IAnimatedMesh.h" +#include "SkinnedMesh.h" +#include "Transform.h" #include "matrix4.h" namespace irr @@ -54,9 +57,11 @@ public: //! returns the current loop mode bool getLoopMode() const override; - //! Sets a callback interface which will be called if an animation - //! playback has ended. Set this to 0 to disable the callback again. - void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) override; + void setOnAnimateCallback( + const std::function &cb) override + { + OnAnimateCallback = cb; + } //! sets the speed with which the animation is played //! NOTE: setMesh will also change this value and set it to the default speed of the mesh @@ -117,15 +122,16 @@ public: //! updates the absolute position based on the relative and the parents position void updateAbsolutePosition() override; - //! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set) - void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) override; - - //! Sets the transition time in seconds (note: This needs to enable joints, and setJointmode maybe set to 2) + //! Sets the transition time in seconds (note: This needs to enable joints) //! you must call animateJoints(), or the mesh will not animate void setTransitionTime(f32 Time) override; + void updateJointSceneNodes(const std::vector &transforms); + //! updates the joint positions of this mesh - void animateJoints(bool CalculateAbsolutePositions = true) override; + void animateJoints() override; + + void addJoints(); //! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected) void setRenderFromIdentity(bool On) override; @@ -142,6 +148,7 @@ private: void buildFrameNr(u32 timeMs); void checkJoints(); + void copyOldTransforms(); void beginTransition(); core::array Materials; @@ -158,19 +165,30 @@ private: f32 Transiting; // is mesh transiting (plus cache of TransitionTime) f32 TransitingBlend; // 0-1, calculated on buildFrameNr - // 0-unused, 1-get joints only, 2-set joints only, 3-move and set - E_JOINT_UPDATE_ON_RENDER JointMode; bool JointsUsed; bool Looping; bool ReadOnlyMaterials; bool RenderFromIdentity; - IAnimationEndCallBack *LoopCallBack; s32 PassCount; + std::function OnAnimateCallback; - std::vector JointChildSceneNodes; - core::array PretransitingSave; + struct PerJointData { + std::vector SceneNodes; + std::vector GlobalMatrices; + std::vector> PreTransSaves; + void setN(u16 n) { + SceneNodes.clear(); + SceneNodes.resize(n); + GlobalMatrices.clear(); + GlobalMatrices.resize(n); + PreTransSaves.clear(); + PreTransSaves.resize(n); + } + }; + + PerJointData PerJoint; }; } // end namespace scene diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index 51342f451..6e3e14791 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -143,31 +143,25 @@ bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint) os::Printer::log(logStr.c_str(), joint->Name.value_or("").c_str(), ELL_DEBUG); #endif - f32 position[3], scale[3], rotation[4]; + core::Transform transform; + { + f32 t[3], s[3], r[4]; - readFloats(position, 3); - readFloats(scale, 3); - readFloats(rotation, 4); + readFloats(t, 3); + readFloats(s, 3); + readFloats(r, 4); - joint->Animatedposition = core::vector3df(position[0], position[1], position[2]); - joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]); - joint->Animatedrotation = core::quaternion(rotation[1], rotation[2], rotation[3], rotation[0]); - - // Build LocalMatrix: - - core::matrix4 positionMatrix; - positionMatrix.setTranslation(joint->Animatedposition); - core::matrix4 scaleMatrix; - scaleMatrix.setScale(joint->Animatedscale); - core::matrix4 rotationMatrix; - joint->Animatedrotation.getMatrix_transposed(rotationMatrix); - - joint->LocalMatrix = positionMatrix * rotationMatrix * scaleMatrix; + joint->transform = transform = { + {t[0], t[1], t[2]}, + {r[1], r[2], r[3], r[0]}, + {s[0], s[1], s[2]}, + }; + } if (inJoint) - joint->GlobalMatrix = inJoint->GlobalMatrix * joint->LocalMatrix; + joint->GlobalMatrix = inJoint->GlobalMatrix * transform.buildMatrix(); else - joint->GlobalMatrix = joint->LocalMatrix; + joint->GlobalMatrix = transform.buildMatrix(); while (B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats { diff --git a/irr/src/CBoneSceneNode.cpp b/irr/src/CBoneSceneNode.cpp deleted file mode 100644 index 7aa637094..000000000 --- a/irr/src/CBoneSceneNode.cpp +++ /dev/null @@ -1,86 +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 "CBoneSceneNode.h" - -#include - -namespace irr -{ -namespace scene -{ - -//! constructor -CBoneSceneNode::CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id, - u32 boneIndex, const std::optional &boneName) : - IBoneSceneNode(parent, mgr, id), - BoneIndex(boneIndex), - AnimationMode(EBAM_AUTOMATIC), SkinningSpace(EBSS_LOCAL) -{ - setName(boneName); -} - -//! Returns the index of the bone -u32 CBoneSceneNode::getBoneIndex() const -{ - return BoneIndex; -} - -//! Sets the animation mode of the bone. Returns true if successful. -bool CBoneSceneNode::setAnimationMode(E_BONE_ANIMATION_MODE mode) -{ - AnimationMode = mode; - return true; -} - -//! Gets the current animation mode of the bone -E_BONE_ANIMATION_MODE CBoneSceneNode::getAnimationMode() const -{ - return AnimationMode; -} - -//! returns the axis aligned bounding box of this node -const core::aabbox3d &CBoneSceneNode::getBoundingBox() const -{ - return Box; -} - -/* -//! Returns the relative transformation of the scene node. -core::matrix4 CBoneSceneNode::getRelativeTransformation() const -{ - return core::matrix4(); // RelativeTransformation; -} -*/ - -void CBoneSceneNode::OnAnimate(u32 timeMs) -{ - if (IsVisible) { - // update absolute position - // updateAbsolutePosition(); - - // perform the post render process on all children - ISceneNodeList::iterator it = Children.begin(); - for (; it != Children.end(); ++it) - (*it)->OnAnimate(timeMs); - } -} - -void CBoneSceneNode::helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node) -{ - Node->updateAbsolutePosition(); - - ISceneNodeList::const_iterator it = Node->getChildren().begin(); - for (; it != Node->getChildren().end(); ++it) { - helper_updateAbsolutePositionOfAllChildren((*it)); - } -} - -void CBoneSceneNode::updateAbsolutePositionOfAllChildren() -{ - helper_updateAbsolutePositionOfAllChildren(this); -} - -} // namespace scene -} // namespace irr diff --git a/irr/src/CBoneSceneNode.h b/irr/src/CBoneSceneNode.h index d900570db..4151f7372 100644 --- a/irr/src/CBoneSceneNode.h +++ b/irr/src/CBoneSceneNode.h @@ -7,6 +7,8 @@ // Used with SkinnedMesh and IAnimatedMeshSceneNode, for boned meshes #include "IBoneSceneNode.h" +#include "Transform.h" +#include "matrix4.h" #include @@ -21,49 +23,48 @@ public: //! constructor CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1, u32 boneIndex = 0, - const std::optional &boneName = std::nullopt); - - //! Returns the index of the bone - u32 getBoneIndex() const override; - - //! Sets the animation mode of the bone. Returns true if successful. - bool setAnimationMode(E_BONE_ANIMATION_MODE mode) override; - - //! Gets the current animation mode of the bone - E_BONE_ANIMATION_MODE getAnimationMode() const override; - - //! returns the axis aligned bounding box of this node - const core::aabbox3d &getBoundingBox() const override; - - /* - //! Returns the relative transformation of the scene node. - //core::matrix4 getRelativeTransformation() const override; - */ - - void OnAnimate(u32 timeMs) override; - - void updateAbsolutePositionOfAllChildren() override; - - //! How the relative transformation of the bone is used - void setSkinningSpace(E_BONE_SKINNING_SPACE space) override + const std::optional &boneName = std::nullopt, + const core::Transform &transform = {}, + const std::optional &matrix = std::nullopt) : + IBoneSceneNode(parent, mgr, id, boneIndex, boneName), + Matrix(matrix) { - SkinningSpace = space; + setTransform(transform); } - E_BONE_SKINNING_SPACE getSkinningSpace() const override + void setTransform(const core::Transform &transform) { - return SkinningSpace; + setPosition(transform.translation); + { + core::vector3df euler; + auto rot = transform.rotation; + // Invert to be consistent with setRotationDegrees + rot.makeInverse(); + rot.toEuler(euler); + setRotation(euler * core::RADTODEG); + } + setScale(transform.scale); } -private: - void helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node); + core::Transform getTransform() const + { + return { + getPosition(), + core::quaternion(getRotation() * core::DEGTORAD).makeInverse(), + getScale() + }; + } - u32 BoneIndex; + core::matrix4 getRelativeTransformation() const override + { + if (Matrix) + return *Matrix; + return IBoneSceneNode::getRelativeTransformation(); + } - core::aabbox3d Box{-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f}; - - E_BONE_ANIMATION_MODE AnimationMode; - E_BONE_SKINNING_SPACE SkinningSpace; + //! Some file formats alternatively let bones specify a transformation matrix. + //! If this is set, it overrides the TRS properties. + std::optional Matrix; }; } // end namespace scene diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 3f2096f40..53ae1b580 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -539,34 +539,25 @@ static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMes mat[i] = static_cast(m[i]); mat = convertHandedness(mat); - // Decompose the matrix into translation, scale, and rotation. - joint->Animatedposition = mat.getTranslation(); - - auto scale = mat.getScale(); - joint->Animatedscale = scale; - joint->Animatedrotation = mat.getRotationRadians(scale); - // Invert the rotation because it is applied using `getMatrix_transposed`, - // which again inverts. - joint->Animatedrotation.makeInverse(); - + // Note: "When a node is targeted for animation [...], + // only TRS properties MAY be present; matrix MUST NOT be present." + // Thus we MUST NOT do any decomposition, which in general need not exist. + joint->transform = mat; return mat; } static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, SkinnedMesh::SJoint *joint) { - const auto &trans = trs.translation; - const auto &rot = trs.rotation; - const auto &scale = trs.scale; - core::matrix4 transMat; - 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(joint->Animatedscale); - return transMat * rotMat * scaleMat; + const auto &t = trs.translation; + const auto &r = trs.rotation; + const auto &s = trs.scale; + core::Transform transform{ + convertHandedness(core::vector3df(t[0], t[1], t[2])), + convertHandedness(core::quaternion(r[0], r[1], r[2], r[3])), + core::vector3df(s[0], s[1], s[2]), + }; + joint->transform = transform; + return transform.buildMatrix(); } static core::matrix4 loadTransform(std::optional> transform, @@ -584,8 +575,7 @@ void SelfType::MeshExtractor::loadNode( const auto &node = m_gltf_model.nodes->at(nodeIdx); auto *joint = m_irr_model->addJoint(parent); const core::matrix4 transform = loadTransform(node.transform, joint); - joint->LocalMatrix = transform; - joint->GlobalMatrix = parent ? parent->GlobalMatrix * joint->LocalMatrix : joint->LocalMatrix; + joint->GlobalMatrix = parent ? parent->GlobalMatrix * transform : transform; if (node.name.has_value()) { joint->Name = node.name->c_str(); } @@ -642,7 +632,6 @@ 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); bool interpolate = ([&]() { @@ -663,6 +652,11 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) throw std::runtime_error("no animated node"); auto *joint = m_loaded_nodes.at(*channel.target.node); + if (std::holds_alternative(joint->transform)) { + warn("nodes using matrix transforms must not be animated"); + continue; + } + switch (channel.target.path) { case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: { const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 83da89c11..c752d7d31 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -320,7 +320,6 @@ set(IRRMESHLOADER add_library(IRRMESHOBJ OBJECT SkinnedMesh.cpp - CBoneSceneNode.cpp CMeshSceneNode.cpp CAnimatedMeshSceneNode.cpp ${IRRMESHLOADER} diff --git a/irr/src/CMeshCache.cpp b/irr/src/CMeshCache.cpp index 4f7e1203c..9a41aa582 100644 --- a/irr/src/CMeshCache.cpp +++ b/irr/src/CMeshCache.cpp @@ -35,7 +35,7 @@ void CMeshCache::removeMesh(const IMesh *const mesh) if (!mesh) return; for (u32 i = 0; i < Meshes.size(); ++i) { - if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) { + if (Meshes[i].Mesh == mesh) { Meshes[i].Mesh->drop(); Meshes.erase(i); return; @@ -53,7 +53,7 @@ u32 CMeshCache::getMeshCount() const s32 CMeshCache::getMeshIndex(const IMesh *const mesh) const { for (u32 i = 0; i < Meshes.size(); ++i) { - if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) + if (Meshes[i].Mesh == mesh) return (s32)i; } @@ -93,7 +93,7 @@ const io::SNamedPath &CMeshCache::getMeshName(const IMesh *const mesh) const return emptyNamedPath; for (u32 i = 0; i < Meshes.size(); ++i) { - if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) + if (Meshes[i].Mesh == mesh) return Meshes[i].NamedPath; } @@ -115,7 +115,7 @@ bool CMeshCache::renameMesh(u32 index, const io::path &name) bool CMeshCache::renameMesh(const IMesh *const mesh, const io::path &name) { for (u32 i = 0; i < Meshes.size(); ++i) { - if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) { + if (Meshes[i].Mesh == mesh) { Meshes[i].NamedPath.setPath(name); Meshes.sort(); return true; diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index 659e8dff8..535b74686 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -6,7 +6,6 @@ #include "SkinnedMesh.h" #include "SMesh.h" #include "CMeshBuffer.h" -#include "SAnimatedMesh.h" #include "os.h" #include @@ -178,34 +177,5 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const return clone; } -//! Returns amount of polygons in mesh. -s32 CMeshManipulator::getPolyCount(scene::IMesh *mesh) const -{ - if (!mesh) - return 0; - - s32 trianglecount = 0; - - for (u32 g = 0; g < mesh->getMeshBufferCount(); ++g) - trianglecount += mesh->getMeshBuffer(g)->getIndexCount() / 3; - - return trianglecount; -} - -//! Returns amount of polygons in mesh. -s32 CMeshManipulator::getPolyCount(scene::IAnimatedMesh *mesh) const -{ - if (mesh && mesh->getMaxFrameNumber() != 0) - return getPolyCount(mesh->getMesh(0)); - - return 0; -} - -//! create a new AnimatedMesh and adds the mesh to it -IAnimatedMesh *CMeshManipulator::createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const -{ - return new SAnimatedMesh(mesh, type); -} - } // end namespace scene } // end namespace irr diff --git a/irr/src/CMeshManipulator.h b/irr/src/CMeshManipulator.h index 0377d6a3a..c5632512d 100644 --- a/irr/src/CMeshManipulator.h +++ b/irr/src/CMeshManipulator.h @@ -31,15 +31,6 @@ public: //! Clones a static IMesh into a modifiable SMesh. SMesh *createMeshCopy(scene::IMesh *mesh) const override; - - //! Returns amount of polygons in mesh. - s32 getPolyCount(scene::IMesh *mesh) const override; - - //! Returns amount of polygons in mesh. - s32 getPolyCount(scene::IAnimatedMesh *mesh) const override; - - //! create a new AnimatedMesh and adds the mesh to it - IAnimatedMesh *createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const override; }; } // end namespace scene diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index 246f414de..f7d012913 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -1238,7 +1238,7 @@ void CNullDriver::addOcclusionQuery(scene::ISceneNode *node, const scene::IMesh else if (node->getType() == scene::ESNT_MESH) mesh = static_cast(node)->getMesh(); else - mesh = static_cast(node)->getMesh()->getMesh(0); + mesh = static_cast(node)->getMesh(); if (!mesh) return; } diff --git a/irr/src/COBJMeshFileLoader.cpp b/irr/src/COBJMeshFileLoader.cpp index 5c7f38950..bc51c10f2 100644 --- a/irr/src/COBJMeshFileLoader.cpp +++ b/irr/src/COBJMeshFileLoader.cpp @@ -7,7 +7,6 @@ #include "IVideoDriver.h" #include "SMesh.h" #include "SMeshBuffer.h" -#include "SAnimatedMesh.h" #include "IReadFile.h" #include "fast_atof.h" #include "coreutil.h" @@ -272,23 +271,19 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file) } } - // Create the Animated mesh if there's anything in the mesh - SAnimatedMesh *animMesh = 0; - if (0 != mesh->getMeshBufferCount()) { - mesh->recalculateBoundingBox(); - animMesh = new SAnimatedMesh(); - animMesh->Type = EAMT_OBJ; - animMesh->addMesh(mesh); - animMesh->recalculateBoundingBox(); - } - // Clean up the allocate obj file contents delete[] buf; // more cleaning up cleanUp(); - mesh->drop(); - return animMesh; + // Nothing in the mesh + if (mesh->getMeshBufferCount() == 0) { + mesh->drop(); + return nullptr; + } + + mesh->recalculateBoundingBox(); + return mesh; } //! Read RGB color diff --git a/irr/src/CSceneManager.cpp b/irr/src/CSceneManager.cpp index 05f8de71a..b0b82b772 100644 --- a/irr/src/CSceneManager.cpp +++ b/irr/src/CSceneManager.cpp @@ -8,7 +8,6 @@ #include "CSceneManager.h" #include "IVideoDriver.h" #include "IFileSystem.h" -#include "SAnimatedMesh.h" #include "CMeshCache.h" #include "IGUIEnvironment.h" #include "IMaterialRenderer.h" diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index d93502d6b..f2c4e94e6 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -4,6 +4,7 @@ #include "CXMeshFileLoader.h" #include "SkinnedMesh.h" +#include "Transform.h" #include "os.h" #include "fast_atof.h" @@ -513,6 +514,7 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent) if (n.has_value()) { JointID = *n; joint = AnimatedMesh->getAllJoints()[JointID]; + joint->setParent(Parent); } } @@ -527,8 +529,6 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent) #ifdef _XREADER_DEBUG os::Printer::log("using joint ", name.c_str(), ELL_DEBUG); #endif - if (Parent) - Parent->Children.push_back(joint); } // Now inside a frame. @@ -552,12 +552,10 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent) if (!parseDataObjectFrame(joint)) return false; } else if (objectName == "FrameTransformMatrix") { - if (!parseDataObjectTransformationMatrix(joint->LocalMatrix)) + core::matrix4 matrix; + if (!parseDataObjectTransformationMatrix(matrix)) return false; - - // joint->LocalAnimatedMatrix - // joint->LocalAnimatedMatrix.makeInverse(); - // joint->LocalMatrix=tmp*joint->LocalAnimatedMatrix; + joint->transform = matrix; } else if (objectName == "Mesh") { /* frame.Meshes.push_back(SXMesh()); diff --git a/irr/src/SkinnedMesh.cpp b/irr/src/SkinnedMesh.cpp index 938a50e17..cf58121ba 100644 --- a/irr/src/SkinnedMesh.cpp +++ b/irr/src/SkinnedMesh.cpp @@ -7,7 +7,15 @@ #include "CBoneSceneNode.h" #include "IAnimatedMeshSceneNode.h" #include "SSkinMeshBuffer.h" +#include "Transform.h" +#include "aabbox3d.h" +#include "irrMath.h" +#include "matrix4.h" #include "os.h" +#include "vector3d.h" +#include +#include +#include #include #include @@ -48,183 +56,77 @@ void SkinnedMesh::setAnimationSpeed(f32 fps) FramesPerSecond = fps; } -//! returns the animated mesh based -IMesh *SkinnedMesh::getMesh(f32 frame) -{ - // animate(frame,startFrameLoop, endFrameLoop); - if (frame == -1) - return this; +// Keyframe Animation - animateMesh(frame); - skinMesh(); - return this; + +using VariantTransform = SkinnedMesh::SJoint::VariantTransform; +std::vector SkinnedMesh::animateMesh(f32 frame) +{ + assert(HasAnimation); + std::vector result; + result.reserve(AllJoints.size()); + for (auto *joint : AllJoints) + result.push_back(joint->animate(frame)); + return result; } -//-------------------------------------------------------------------------- -// Keyframe Animation -//-------------------------------------------------------------------------- - -//! Animates joints based on frame input -void SkinnedMesh::animateMesh(f32 frame) +core::aabbox3df SkinnedMesh::calculateBoundingBox( + const std::vector &global_transforms) { - if (!HasAnimation || LastAnimatedFrame == frame) - return; - - LastAnimatedFrame = frame; - SkinnedLastFrame = false; - - for (auto *joint : AllJoints) { - // The joints can be animated here with no input from their - // parents, but for setAnimationMode extra checks are needed - // to their parents - joint->keys.updateTransform(frame, - joint->Animatedposition, - joint->Animatedrotation, - joint->Animatedscale); + assert(global_transforms.size() == AllJoints.size()); + core::aabbox3df result = StaticPartsBox; + // skeletal animation + for (u16 i = 0; i < AllJoints.size(); ++i) { + auto box = AllJoints[i]->LocalBoundingBox; + global_transforms[i].transformBoxEx(box); + result.addInternalBox(box); } - - // Note: - // LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for - // one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once. - // a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move - - //---------------- - // Temp! - buildAllLocalAnimatedMatrices(); - //----------------- - - updateBoundingBox(); -} - -void SkinnedMesh::buildAllLocalAnimatedMatrices() -{ - for (auto *joint : AllJoints) { - // Could be faster: - - if (!joint->keys.empty()) { - joint->GlobalSkinningSpace = false; - - // 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() --- - f32 *m1 = joint->LocalAnimatedMatrix.pointer(); - core::vector3df &Pos = joint->Animatedposition; - m1[0] += Pos.X * m1[3]; - m1[1] += Pos.Y * m1[3]; - m1[2] += Pos.Z * m1[3]; - m1[4] += Pos.X * m1[7]; - m1[5] += Pos.Y * m1[7]; - m1[6] += Pos.Z * m1[7]; - m1[8] += Pos.X * m1[11]; - m1[9] += Pos.Y * m1[11]; - m1[10] += Pos.Z * m1[11]; - m1[12] += Pos.X * m1[15]; - m1[13] += Pos.Y * m1[15]; - m1[14] += Pos.Z * m1[15]; - // ----------------------------------- - - if (!joint->keys.scale.empty()) { - /* - core::matrix4 scaleMatrix; - scaleMatrix.setScale(joint->Animatedscale); - joint->LocalAnimatedMatrix *= scaleMatrix; - */ - - // -------- joint->LocalAnimatedMatrix *= scaleMatrix ----------------- - core::matrix4 &mat = joint->LocalAnimatedMatrix; - mat[0] *= joint->Animatedscale.X; - mat[1] *= joint->Animatedscale.X; - mat[2] *= joint->Animatedscale.X; - mat[3] *= joint->Animatedscale.X; - mat[4] *= joint->Animatedscale.Y; - mat[5] *= joint->Animatedscale.Y; - mat[6] *= joint->Animatedscale.Y; - mat[7] *= joint->Animatedscale.Y; - mat[8] *= joint->Animatedscale.Z; - mat[9] *= joint->Animatedscale.Z; - mat[10] *= joint->Animatedscale.Z; - mat[11] *= joint->Animatedscale.Z; - // ----------------------------------- - } - } else { - joint->LocalAnimatedMatrix = joint->LocalMatrix; + // rigid animation + for (u16 i = 0; i < AllJoints.size(); ++i) { + for (u32 j : AllJoints[i]->AttachedMeshes) { + auto box = (*SkinningBuffers)[j]->BoundingBox; + global_transforms[i].transformBoxEx(box); + result.addInternalBox(box); } } - SkinnedLastFrame = false; + return result; } -void SkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint) +// Software Skinning + +void SkinnedMesh::skinMesh(const std::vector &global_matrices) { - if (!joint) { - for (auto *rootJoint : RootJoints) - buildAllGlobalAnimatedMatrices(rootJoint, 0); - return; - } else { - // Find global matrix... - if (!parentJoint || joint->GlobalSkinningSpace) - joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix; - else - joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix; - } - - for (auto *childJoint : joint->Children) - buildAllGlobalAnimatedMatrices(childJoint, joint); -} - -//-------------------------------------------------------------------------- -// Software Skinning -//-------------------------------------------------------------------------- - -//! Preforms a software skin on this mesh based of joint positions -void SkinnedMesh::skinMesh() -{ - if (!HasAnimation || SkinnedLastFrame) + if (!HasAnimation) return; - //---------------- - // This is marked as "Temp!". A shiny dubloon to whomever can tell me why. - buildAllGlobalAnimatedMatrices(); - //----------------- - - SkinnedLastFrame = true; - if (!HardwareSkinning) { - // rigid animation - for (auto *joint : AllJoints) { - for (u32 attachedMeshIdx : joint->AttachedMeshes) { - SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx]; - Buffer->Transformation = joint->GlobalAnimatedMatrix; - } + // rigid animation + for (size_t i = 0; i < AllJoints.size(); ++i) { + auto *joint = AllJoints[i]; + for (u32 attachedMeshIdx : joint->AttachedMeshes) { + SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx]; + Buffer->Transformation = global_matrices[i]; } - - // clear skinning helper array - for (std::vector &buf : Vertices_Moved) - std::fill(buf.begin(), buf.end(), false); - - // skin starting with the root joints - for (auto *rootJoint : RootJoints) - skinJoint(rootJoint, 0); - - for (auto *buffer : *SkinningBuffers) - buffer->setDirty(EBT_VERTEX); } - updateBoundingBox(); -} -void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) -{ - if (joint->Weights.size()) { - // Find this joints pull on vertices... + // clear skinning helper array + for (std::vector &buf : Vertices_Moved) + std::fill(buf.begin(), buf.end(), false); + + // skin starting with the root joints + for (size_t i = 0; i < AllJoints.size(); ++i) { + auto *joint = AllJoints[i]; + if (joint->Weights.empty()) + continue; + + // Find this joints pull on vertices // Note: It is assumed that the global inversed matrix has been calculated at this point. - core::matrix4 jointVertexPull = joint->GlobalAnimatedMatrix * joint->GlobalInversedMatrix.value(); + core::matrix4 jointVertexPull = global_matrices[i] * joint->GlobalInversedMatrix.value(); core::vector3df thisVertexMove, thisNormalMove; auto &buffersUsed = *SkinningBuffers; - // Skin Vertices Positions and Normals... + // Skin Vertices, Positions and Normals for (const auto &weight : joint->Weights) { // Pull this vertex... jointVertexPull.transformVect(thisVertexMove, weight.StaticPos); @@ -251,14 +153,11 @@ void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) //*(weight._Pos) += thisVertexMove * weight.strength; } - - buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated(); } } - // Skin all children - for (auto *childJoint : joint->Children) - skinJoint(childJoint, joint); + for (auto *buffer : *SkinningBuffers) + buffer->setDirty(EBT_VERTEX); } //! Gets joint count. @@ -310,7 +209,7 @@ IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const if (LocalBuffers[i]->getMaterial() == material) return LocalBuffers[i]; } - return 0; + return nullptr; } u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const @@ -337,29 +236,6 @@ void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer) LocalBuffers[i]->setDirty(buffer); } -//! (This feature is not implemented in irrlicht yet) -bool SkinnedMesh::setHardwareSkinning(bool on) -{ - if (HardwareSkinning != on) { - if (on) { - - // set mesh to static pose... - for (auto *joint : AllJoints) { - for (const auto &weight : joint->Weights) { - const u16 buffer_id = weight.buffer_id; - const u32 vertex_id = weight.vertex_id; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = weight.StaticPos; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal; - LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated(); - } - } - } - - HardwareSkinning = on; - } - return HardwareSkinning; -} - void SkinnedMesh::refreshJointCache() { // copy cache from the mesh... @@ -384,113 +260,192 @@ void SkinnedMesh::resetAnimation() LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal; } } - SkinnedLastFrame = false; - LastAnimatedFrame = -1; } -void SkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint) +//! Turns the given array of local matrices into an array of global matrices +//! by multiplying with respective parent matrices. +void SkinnedMesh::calculateGlobalMatrices(std::vector &matrices) const { - if (!joint && parentJoint) // bit of protection from endless loops - return; - - // Go through the root bones - if (!joint) { - for (auto *rootJoint : RootJoints) - calculateGlobalMatrices(rootJoint, nullptr); - return; + // Note that the joints are topologically sorted. + for (u16 i = 0; i < AllJoints.size(); ++i) { + if (auto parent_id = AllJoints[i]->ParentJointID) { + matrices[i] = matrices[*parent_id] * matrices[i]; + } } - - if (!parentJoint) - joint->GlobalMatrix = joint->LocalMatrix; - else - joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix; - - joint->LocalAnimatedMatrix = joint->LocalMatrix; - joint->GlobalAnimatedMatrix = joint->GlobalMatrix; - - if (!joint->GlobalInversedMatrix.has_value()) { // might be pre calculated - joint->GlobalInversedMatrix = joint->GlobalMatrix; - joint->GlobalInversedMatrix->makeInverse(); // slow - } - - for (auto *childJoint : joint->Children) - calculateGlobalMatrices(childJoint, joint); - SkinnedLastFrame = false; } -void SkinnedMesh::checkForAnimation() +bool SkinnedMesh::checkForAnimation() const { - // Check for animation... - HasAnimation = false; for (auto *joint : AllJoints) { if (!joint->keys.empty()) { - HasAnimation = true; - break; + return true; } } - // meshes with weights, are still counted as animated for ragdolls, etc - if (!HasAnimation) { - for (auto *joint : AllJoints) { - if (joint->Weights.size()) { - HasAnimation = true; - break; + // meshes with weights are animatable + for (auto *joint : AllJoints) { + if (!joint->Weights.empty()) { + return true; + } + } + + return false; +} + +void SkinnedMesh::prepareForSkinning() +{ + HasAnimation = checkForAnimation(); + if (!HasAnimation || PreparedForSkinning) + return; + + PreparedForSkinning = true; + + EndFrame = 0.0f; + for (const auto *joint : AllJoints) { + EndFrame = std::max(EndFrame, joint->keys.getEndFrame()); + } + + for (auto *joint : AllJoints) { + for (auto &weight : joint->Weights) { + const u16 buffer_id = weight.buffer_id; + const u32 vertex_id = weight.vertex_id; + + // check for invalid ids + if (buffer_id >= LocalBuffers.size()) { + os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING); + weight.buffer_id = weight.vertex_id = 0; + } else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) { + os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING); + weight.buffer_id = weight.vertex_id = 0; } } } - if (HasAnimation) { - EndFrame = 0.0f; - for (const auto *joint : AllJoints) { - EndFrame = std::max(EndFrame, joint->keys.getEndFrame()); + for (u32 i = 0; i < Vertices_Moved.size(); ++i) + for (u32 j = 0; j < Vertices_Moved[i].size(); ++j) + Vertices_Moved[i][j] = false; + + // For skinning: cache weight values for speed + for (auto *joint : AllJoints) { + for (auto &weight : joint->Weights) { + const u16 buffer_id = weight.buffer_id; + const u32 vertex_id = weight.vertex_id; + + weight.Moved = &Vertices_Moved[buffer_id][vertex_id]; + weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos; + weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal; } } - if (HasAnimation && !PreparedForSkinning) { - PreparedForSkinning = true; + normalizeWeights(); - // check for bugs: - for (auto *joint : AllJoints) { - for (auto &weight : joint->Weights) { - const u16 buffer_id = weight.buffer_id; - const u32 vertex_id = weight.vertex_id; + for (auto *joint : AllJoints) { + joint->keys.cleanup(); + } +} - // check for invalid ids - if (buffer_id >= LocalBuffers.size()) { - os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING); - weight.buffer_id = weight.vertex_id = 0; - } else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) { - os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING); - weight.buffer_id = weight.vertex_id = 0; +void SkinnedMesh::calculateStaticBoundingBox() +{ + std::vector> animated(getMeshBufferCount()); + for (u32 mb = 0; mb < getMeshBufferCount(); mb++) + animated[mb] = std::vector(getMeshBuffer(mb)->getVertexCount()); + + for (auto *joint : AllJoints) { + for (auto &weight : joint->Weights) { + const u16 buffer_id = weight.buffer_id; + const u32 vertex_id = weight.vertex_id; + animated[buffer_id][vertex_id] = true; + } + } + + bool first = true; + for (u16 mb = 0; mb < getMeshBufferCount(); mb++) { + for (u32 v = 0; v < getMeshBuffer(mb)->getVertexCount(); v++) { + if (!animated[mb][v]) { + auto pos = getMeshBuffer(mb)->getVertexBuffer()->getPosition(v); + if (!first) { + StaticPartsBox.addInternalPoint(pos); + } else { + StaticPartsBox.reset(pos); + first = false; } } } + } +} - // An array used in skinning - - for (u32 i = 0; i < Vertices_Moved.size(); ++i) - for (u32 j = 0; j < Vertices_Moved[i].size(); ++j) - Vertices_Moved[i][j] = false; - - // For skinning: cache weight values for speed - - for (auto *joint : AllJoints) { - for (auto &weight : joint->Weights) { - const u16 buffer_id = weight.buffer_id; - const u32 vertex_id = weight.vertex_id; - - weight.Moved = &Vertices_Moved[buffer_id][vertex_id]; - weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos; - weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal; - - // weight._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos; +void SkinnedMesh::calculateJointBoundingBoxes() +{ + for (auto *joint : AllJoints) { + bool first = true; + for (auto &weight : joint->Weights) { + if (weight.strength < 1e-6) + continue; + auto pos = weight.StaticPos; + joint->GlobalInversedMatrix.value().transformVect(pos); + if (!first) { + joint->LocalBoundingBox.addInternalPoint(pos); + } else { + joint->LocalBoundingBox.reset(pos); + first = false; } } - - // normalize weights - normalizeWeights(); } - SkinnedLastFrame = false; +} + +void SkinnedMesh::calculateBufferBoundingBoxes() +{ + for (u32 j = 0; j < LocalBuffers.size(); ++j) { + // If we use skeletal animation, this will just be a bounding box of the static pose; + // if we use rigid animation, this will correctly transform the points first. + LocalBuffers[j]->recalculateBoundingBox(); + } +} + +void SkinnedMesh::recalculateBaseBoundingBoxes() { + calculateStaticBoundingBox(); + calculateJointBoundingBoxes(); + calculateBufferBoundingBoxes(); +} + +void SkinnedMesh::topoSortJoints() +{ + size_t n = AllJoints.size(); + + std::vector new_to_old_id; + + std::vector> children(n); + for (u16 i = 0; i < n; ++i) { + if (auto parentId = AllJoints[i]->ParentJointID) + children[*parentId].push_back(i); + else + new_to_old_id.push_back(i); + } + + // Levelorder + for (u16 i = 0; i < n; ++i) { + new_to_old_id.insert(new_to_old_id.end(), + children[new_to_old_id[i]].begin(), + children[new_to_old_id[i]].end()); + } + + std::vector old_to_new_id(n); + for (u16 i = 0; i < n; ++i) + old_to_new_id[new_to_old_id[i]] = i; + + std::vector joints(n); + for (u16 i = 0; i < n; ++i) { + joints[i] = AllJoints[new_to_old_id[i]]; + joints[i]->JointID = i; + if (auto parentId = joints[i]->ParentJointID) + joints[i]->ParentJointID = old_to_new_id[*parentId]; + } + AllJoints = std::move(joints); + + for (u16 i = 0; i < n; ++i) { + if (auto pjid = AllJoints[i]->ParentJointID) + assert(*pjid < i); + } } //! called by loader after populating with mesh and bone data @@ -498,98 +453,44 @@ SkinnedMesh *SkinnedMeshBuilder::finalize() { os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG); - // Make sure we recalc the next frame - LastAnimatedFrame = -1; - SkinnedLastFrame = false; - - // calculate bounding box - for (auto *buffer : LocalBuffers) { - buffer->recalculateBoundingBox(); - } - - if (AllJoints.size() || RootJoints.size()) { - // populate AllJoints or RootJoints, depending on which is empty - if (RootJoints.empty()) { - - for (auto *joint : AllJoints) { - - bool foundParent = false; - for (const auto *parentJoint : AllJoints) { - for (const auto *childJoint : parentJoint->Children) { - if (childJoint == joint) - foundParent = true; - } - } - - if (!foundParent) - RootJoints.push_back(joint); - } - } else { - AllJoints = RootJoints; - } - } - - // Set array sizes... + topoSortJoints(); + // Set array sizes for (u32 i = 0; i < LocalBuffers.size(); ++i) { Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount()); } - checkForAnimation(); + prepareForSkinning(); - if (HasAnimation) { - for (auto *joint : AllJoints) { - joint->keys.cleanup(); - } - } - - // Needed for animation and skinning... - - calculateGlobalMatrices(0, 0); - - // rigid animation for non animated meshes + std::vector matrices; + matrices.reserve(AllJoints.size()); for (auto *joint : AllJoints) { + if (const auto *matrix = std::get_if(&joint->transform)) + matrices.push_back(*matrix); + else + matrices.push_back(std::get(joint->transform).buildMatrix()); + } + calculateGlobalMatrices(matrices); + + for (size_t i = 0; i < AllJoints.size(); ++i) { + auto *joint = AllJoints[i]; + if (!joint->GlobalInversedMatrix) { + joint->GlobalInversedMatrix = matrices[i]; + joint->GlobalInversedMatrix->makeInverse(); + } + // rigid animation for non animated meshes for (u32 attachedMeshIdx : joint->AttachedMeshes) { SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx]; - Buffer->Transformation = joint->GlobalAnimatedMatrix; + Buffer->Transformation = matrices[i]; } } - // calculate bounding box - if (LocalBuffers.empty()) - BoundingBox.reset(0, 0, 0); - else { - irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox); - LocalBuffers[0]->Transformation.transformBoxEx(bb); - BoundingBox.reset(bb); - - for (u32 j = 1; j < LocalBuffers.size(); ++j) { - bb = LocalBuffers[j]->BoundingBox; - LocalBuffers[j]->Transformation.transformBoxEx(bb); - - BoundingBox.addInternalBox(bb); - } - } + recalculateBaseBoundingBoxes(); + StaticPoseBox = calculateBoundingBox(matrices); return this; } -void SkinnedMesh::updateBoundingBox() -{ - if (!SkinningBuffers) - return; - - BoundingBox.reset(0, 0, 0); - - for (auto *buffer : *SkinningBuffers) { - buffer->recalculateBoundingBox(); - core::aabbox3df bb = buffer->BoundingBox; - buffer->Transformation.transformBoxEx(bb); - - BoundingBox.addInternalBox(bb); - } -} - scene::SSkinMeshBuffer *SkinnedMeshBuilder::addMeshBuffer() { scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer(); @@ -607,14 +508,10 @@ void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf) SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent) { SJoint *joint = new SJoint; + joint->setParent(parent); + joint->JointID = AllJoints.size(); AllJoints.push_back(joint); - if (!parent) { - // Add root joints to array in finalize() - } else { - // Set parent (Be careful of the mesh loader also setting the parent) - parent->Children.push_back(joint); - } return joint; } @@ -684,73 +581,6 @@ void SkinnedMesh::normalizeWeights() } } -void SkinnedMesh::recoverJointsFromMesh(std::vector &jointChildSceneNodes) -{ - for (u32 i = 0; i < AllJoints.size(); ++i) { - IBoneSceneNode *node = jointChildSceneNodes[i]; - SJoint *joint = AllJoints[i]; - node->setPosition(joint->LocalAnimatedMatrix.getTranslation()); - node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees()); - node->setScale(joint->LocalAnimatedMatrix.getScale()); - - node->updateAbsolutePosition(); - } -} - -void SkinnedMesh::transferJointsToMesh(const std::vector &jointChildSceneNodes) -{ - for (u32 i = 0; i < AllJoints.size(); ++i) { - const IBoneSceneNode *const node = jointChildSceneNodes[i]; - SJoint *joint = AllJoints[i]; - - joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation()); - joint->LocalAnimatedMatrix.setTranslation(node->getPosition()); - joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale()); - - joint->GlobalSkinningSpace = (node->getSkinningSpace() == EBSS_GLOBAL); - } - // Make sure we recalc the next frame - LastAnimatedFrame = -1; - SkinnedLastFrame = false; -} - -void SkinnedMesh::addJoints(std::vector &jointChildSceneNodes, - IAnimatedMeshSceneNode *node, ISceneManager *smgr) -{ - // Create new joints - for (u32 i = 0; i < AllJoints.size(); ++i) { - jointChildSceneNodes.push_back(new CBoneSceneNode(0, smgr, 0, i, AllJoints[i]->Name)); - } - - // Match up parents - for (u32 i = 0; i < jointChildSceneNodes.size(); ++i) { - const SJoint *const joint = AllJoints[i]; // should be fine - - s32 parentID = -1; - - for (u32 j = 0; (parentID == -1) && (j < AllJoints.size()); ++j) { - if (i != j) { - const SJoint *const parentTest = AllJoints[j]; - for (u32 n = 0; n < parentTest->Children.size(); ++n) { - if (parentTest->Children[n] == joint) { - parentID = j; - break; - } - } - } - } - - IBoneSceneNode *bone = jointChildSceneNodes[i]; - if (parentID != -1) - bone->setParent(jointChildSceneNodes[parentID]); - else - bone->setParent(node); - - bone->drop(); - } - SkinnedLastFrame = false; -} - void SkinnedMesh::convertMeshToTangents() { // now calculate tangents diff --git a/lib/tiniergltf/tiniergltf.hpp b/lib/tiniergltf/tiniergltf.hpp index 06e2f5356..81ac10ec8 100644 --- a/lib/tiniergltf/tiniergltf.hpp +++ b/lib/tiniergltf/tiniergltf.hpp @@ -916,12 +916,7 @@ struct Node { 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 - }) + : transform(TRS{}) { check(o.isObject()); if (o.isMember("camera")) { diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index e0ac5fff0..ecfe0f2de 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -178,15 +178,6 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, matrix.setTextureScale(txs, tys); } -// Evaluate transform chain recursively; irrlicht does not do this for us -static void updatePositionRecursive(scene::ISceneNode *node) -{ - scene::ISceneNode *parent = node->getParent(); - if (parent) - updatePositionRecursive(parent); - node->updateAbsolutePosition(); -} - static bool logOnce(const std::ostringstream &from, std::ostream &log_to) { thread_local std::vector logged; @@ -682,7 +673,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode); m_animated_meshnode->grab(); mesh->drop(); // The scene node took hold of it - m_animated_meshnode->animateJoints(); // Needed for some animations m_animated_meshnode->setScale(m_prop.visual_size); // set vertex colors to ensure alpha is set @@ -693,6 +683,21 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_animated_meshnode->forEachMaterial([this] (auto &mat) { mat.BackfaceCulling = m_prop.backface_culling; }); + + m_animated_meshnode->setOnAnimateCallback([&](f32 dtime) { + for (auto &it : m_bone_override) { + auto* bone = m_animated_meshnode->getJointNode(it.first.c_str()); + if (!bone) + continue; + + BoneOverride &props = it.second; + props.dtime_passed += dtime; + + bone->setPosition(props.getPosition(bone->getPosition())); + bone->setRotation(props.getRotationEulerDeg(bone->getRotation())); + bone->setScale(props.getScale(bone->getScale())); + } + }); } else errorstream<<"GenericCAO::addToScene(): Could not load mesh "<updateAbsolutePosition(); - m_animated_meshnode->animateJoints(); - updateBones(dtime); - } } static void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count) @@ -1394,44 +1386,6 @@ void GenericCAO::updateAnimationSpeed() m_animated_meshnode->setAnimationSpeed(m_animation_speed); } -void GenericCAO::updateBones(f32 dtime) -{ - if (!m_animated_meshnode) - return; - if (m_bone_override.empty()) { - m_animated_meshnode->setJointMode(scene::EJUOR_NONE); - return; - } - - m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render - for (auto &it : m_bone_override) { - std::string bone_name = it.first; - scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); - if (!bone) - continue; - - BoneOverride &props = it.second; - props.dtime_passed += dtime; - - bone->setPosition(props.getPosition(bone->getPosition())); - bone->setRotation(props.getRotationEulerDeg(bone->getRotation())); - bone->setScale(props.getScale(bone->getScale())); - } - - // The following is needed for set_bone_pos to propagate to - // attached objects correctly. - // Irrlicht ought to do this, but doesn't when using EJUOR_CONTROL. - for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) { - auto bone = m_animated_meshnode->getJointNode(i); - // Look for the root bone. - if (bone && bone->getParent() == m_animated_meshnode) { - // Update entire skeleton. - bone->updateAbsolutePositionOfAllChildren(); - break; - } - } -} - void GenericCAO::updateAttachments() { ClientActiveObject *parent = getParent(); @@ -1747,7 +1701,6 @@ void GenericCAO::processMessage(const std::string &data) } else { m_bone_override[bone] = props; } - // updateBones(); now called every step } else if (cmd == AO_CMD_ATTACH_TO) { u16 parent_id = readS16(is); std::string bone = deSerializeString16(is); diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 9762684f6..2e765ab6e 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -286,8 +286,6 @@ public: void updateAnimationSpeed(); - void updateBones(f32 dtime); - void processMessage(const std::string &data) override; bool directReportPunch(v3f dir, const ItemStack *punchitem, diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index 808dcdd18..45b665bba 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -10,10 +10,9 @@ #include #include #include -#include #include #include "S3DVertex.h" -#include "SMesh.h" +#include #include "SMeshBuffer.h" inline static void applyShadeFactor(video::SColor& color, float factor) @@ -97,11 +96,8 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale) mesh->addMeshBuffer(buf); buf->drop(); } - - scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh); - mesh->drop(); - scaleMesh(anim_mesh, scale); // also recalculates bounding box - return anim_mesh; + scaleMesh(mesh, scale); // also recalculates bounding box + return mesh; } template diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 04f1959c1..9d54c3957 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -4,7 +4,6 @@ #include "nodedef.h" -#include "SAnimatedMesh.h" #include "itemdef.h" #if CHECK_CLIENT_BUILD() #include "client/mesh.h" @@ -964,13 +963,6 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc // Note: By freshly reading, we get an unencumbered mesh. if (scene::IMesh *src_mesh = client->getMesh(mesh)) { bool apply_bs = false; - // For frame-animated meshes, always get the first frame, - // which holds a model for which we can eventually get the static pose. - while (auto *src_meshes = dynamic_cast(src_mesh)) { - src_mesh = src_meshes->getMesh(0.0f); - src_mesh->grab(); - src_meshes->drop(); - } if (auto *skinned_mesh = dynamic_cast(src_mesh)) { // Compatibility: Animated meshes, as well as static gltf meshes, are not scaled by BS. // See https://github.com/luanti-org/luanti/pull/16112#issuecomment-2881860329 diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 2a38af68d..159e816c7 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -59,6 +59,8 @@ set (UNITTEST_CLIENT_SRCS ${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_irr_x_mesh_loader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_matrix4.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 index f689838db..6cd6f0f1d 100644 --- a/src/unittest/test_irr_gltf_mesh_loader.cpp +++ b/src/unittest/test_irr_gltf_mesh_loader.cpp @@ -394,36 +394,40 @@ SECTION("simple skin") 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]; + const auto findJoint = [&](const std::function &predicate) { + for (const auto *joint : joints) { + if (predicate(joint)) { + return joint; } } throw std::runtime_error("joint not found"); }; // Check the node hierarchy - const auto parent = findJoint([](auto joint) { - return !joint->Children.empty(); + const auto child = findJoint([&](auto *joint) { + return !!joint->ParentJointID; }); - REQUIRE(parent->Children.size() == 1); - const auto child = parent->Children[0]; - REQUIRE(child != parent); + const auto *parent = joints.at(*child->ParentJointID); 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.setTranslation(-childTranslation); - CHECK(child->GlobalInversedMatrix == inverseBindMatrix); + { + const auto &transform = std::get(parent->transform); + CHECK(transform.translation == v3f(0, 0, 0)); + CHECK(transform.rotation == irr::core::quaternion()); + CHECK(transform.scale == v3f(1, 1, 1)); + CHECK(parent->GlobalInversedMatrix == irr::core::matrix4()); + } + { + const auto &transform = std::get(child->transform); + const v3f translation(0, 1, 0); + CHECK(transform.translation == translation); + CHECK(transform.rotation == irr::core::quaternion()); + CHECK(transform.scale == v3f(1, 1, 1)); + irr::core::matrix4 inverseBindMatrix; + inverseBindMatrix.setTranslation(-translation); + CHECK(child->GlobalInversedMatrix == inverseBindMatrix); + } } SECTION("weights are correct") diff --git a/src/unittest/test_irr_x_mesh_loader.cpp b/src/unittest/test_irr_x_mesh_loader.cpp new file mode 100644 index 000000000..cb61ce7da --- /dev/null +++ b/src/unittest/test_irr_x_mesh_loader.cpp @@ -0,0 +1,111 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "catch_amalgamated.hpp" +#include "content/subgames.h" +#include "filesys.h" + +#include "irrlichttypes.h" +#include "irr_ptr.h" + +#include "EDriverTypes.h" +#include "IFileSystem.h" +#include "IReadFile.h" +#include "ISceneManager.h" +#include "SkinnedMesh.h" +#include "irrlicht.h" + +#include "catch.h" +#include "matrix4.h" + +TEST_CASE("x") { + +const auto gamespec = findSubgame("devtest"); + +if (!gamespec.isValid()) + SKIP(); + +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 + + DIR_DELIM + "testentities" + DIR_DELIM + "models" + DIR_DELIM + "testentities_"; + +SECTION("cool guy") { + const auto *mesh = dynamic_cast(loadMesh(model_stem + "cool_guy.x")); + REQUIRE(mesh); + REQUIRE(mesh->getMeshBufferCount() == 1); + + auto getJointId = [&](auto name) { + return mesh->getJointNumber(name).value(); + }; + + const auto root = getJointId("Root"); + const auto armature = getJointId("Armature"); + const auto armature_body = getJointId("Armature_body"); + const auto armature_arm_r = getJointId("Armature_arm_r"); + + std::vector matrices; + matrices.reserve(mesh->getJointCount()); + for (auto *joint : mesh->getAllJoints()) { + if (const auto *matrix = std::get_if(&joint->transform)) + matrices.push_back(*matrix); + else + matrices.push_back(std::get(joint->transform).buildMatrix()); + } + auto local_matrices = matrices; + mesh->calculateGlobalMatrices(matrices); + + SECTION("joints are topologically sorted") { + REQUIRE(root < armature); + REQUIRE(armature < armature_body); + REQUIRE(armature_body < armature_arm_r); + } + + SECTION("parents are correct") { + const auto get_parent = [&](auto id) { + return mesh->getAllJoints()[id]->ParentJointID; + }; + REQUIRE(!get_parent(root)); + REQUIRE(get_parent(armature).value() == root); + REQUIRE(get_parent(armature_body).value() == armature); + REQUIRE(get_parent(armature_arm_r).value() == armature_body); + } + + SECTION("local matrices are correct") { + REQUIRE(local_matrices[root].equals(core::IdentityMatrix)); + REQUIRE(local_matrices[armature].equals(core::IdentityMatrix)); + REQUIRE(local_matrices[armature_body] == core::matrix4( + -1,0,0,0, + 0,0,1,0, + 0,1,0,0, + 0,2.571201,0,1 + )); + REQUIRE(local_matrices[armature_arm_r] == core::matrix4( + -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 + )); + } + + SECTION("global matrices are correct") { + REQUIRE(matrices[armature_body] == local_matrices[armature_body]); + REQUIRE(matrices[armature_arm_r] == + matrices[armature_body] * local_matrices[armature_arm_r]); + } +} + +driver->closeDevice(); +driver->drop(); +}