diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index 6abd16c5890a0..f1f7fe4a197d3 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -1,5 +1,4 @@ local builtin_shared = ... -local SCALE = 0.667 local facedir_to_euler = { {y = 0, x = 0, z = 0}, @@ -36,9 +35,7 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81 core.register_entity(":__builtin:falling_node", { initial_properties = { - visual = "item", - visual_size = vector.new(SCALE, SCALE, SCALE), - textures = {}, + visual = "node", physical = true, is_visible = false, collide_with_objects = true, @@ -80,41 +77,15 @@ core.register_entity(":__builtin:falling_node", { -- Save liquidtype for falling water self.liquidtype = def.liquidtype - -- Set entity visuals - if def.drawtype == "torchlike" or def.drawtype == "signlike" then - local textures - if def.tiles and def.tiles[1] then - local tile = def.tiles[1] - if type(tile) == "table" then - tile = tile.name - end - if def.drawtype == "torchlike" then - textures = { "("..tile..")^[transformFX", tile } - else - textures = { tile, "("..tile..")^[transformFX" } - end - end - local vsize - if def.visual_scale then - local s = def.visual_scale - vsize = vector.new(s, s, s) - end - self.object:set_properties({ - is_visible = true, - visual = "upright_sprite", - visual_size = vsize, - textures = textures, - glow = def.light_source, - }) - elseif def.drawtype ~= "airlike" then - local itemstring = node.name - if core.is_colored_paramtype(def.paramtype2) then - itemstring = core.itemstring_with_palette(itemstring, node.param2) - end - -- FIXME: solution needed for paramtype2 == "leveled" + -- Set up entity visuals + -- For compatibility with older clients we continue to use "item" visual + -- for simple situations. + local drawtypes = {normal=true, glasslike=true, allfaces=true, nodebox=true} + local p2types = {none=true, facedir=true, ["4dir"]=true} + if drawtypes[def.drawtype] and p2types[def.paramtype2] and def.use_texture_alpha ~= "blend" then -- Calculate size of falling node - local s = {} - s.x = (def.visual_scale or 1) * SCALE + local s = vector.zero() + s.x = (def.visual_scale or 1) * 0.667 s.y = s.x s.z = s.x -- Compensate for wield_scale @@ -125,10 +96,31 @@ core.register_entity(":__builtin:falling_node", { end self.object:set_properties({ is_visible = true, - wield_item = itemstring, + visual = "item", + wield_item = node.name, visual_size = s, glow = def.light_source, }) + -- Rotate as needed + if def.paramtype2 == "facedir" then + local fdir = node.param2 % 32 % 24 + local euler = facedir_to_euler[fdir + 1] + if euler then + self.object:set_rotation(euler) + end + elseif def.paramtype2 == "4dir" then + local fdir = node.param2 % 4 + local euler = facedir_to_euler[fdir + 1] + if euler then + self.object:set_rotation(euler) + end + end + elseif def.drawtype ~= "airlike" then + self.object:set_properties({ + is_visible = true, + node = node, + glow = def.light_source, + }) end -- Set collision box (certain nodeboxes only for now) @@ -148,111 +140,6 @@ core.register_entity(":__builtin:falling_node", { }) end end - - -- Rotate entity - if def.drawtype == "torchlike" then - if (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") - and node.param2 % 8 == 7 then - self.object:set_yaw(-math.pi*0.25) - else - self.object:set_yaw(math.pi*0.25) - end - elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh") - and (def.wield_image == "" or def.wield_image == nil)) - or def.drawtype == "signlike" - or def.drawtype == "mesh" - or def.drawtype == "normal" - or def.drawtype == "nodebox" then - if (def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir") then - local fdir = node.param2 % 32 % 24 - -- Get rotation from a precalculated lookup table - local euler = facedir_to_euler[fdir + 1] - if euler then - self.object:set_rotation(euler) - end - elseif (def.paramtype2 == "4dir" or def.paramtype2 == "color4dir") then - local fdir = node.param2 % 4 - -- Get rotation from a precalculated lookup table - local euler = facedir_to_euler[fdir + 1] - if euler then - self.object:set_rotation(euler) - end - elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and - (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then - local rot = node.param2 % 8 - if (def.drawtype == "signlike" and def.paramtype2 ~= "wallmounted" and def.paramtype2 ~= "colorwallmounted") then - -- Change rotation to "floor" by default for non-wallmounted paramtype2 - rot = 1 - end - local pitch, yaw, roll = 0, 0, 0 - if def.drawtype == "nodebox" or def.drawtype == "mesh" then - if rot == 0 then - pitch, yaw = math.pi/2, 0 - elseif rot == 1 then - pitch, yaw = -math.pi/2, math.pi - elseif rot == 2 then - pitch, yaw = 0, math.pi/2 - elseif rot == 3 then - pitch, yaw = 0, -math.pi/2 - elseif rot == 4 then - pitch, yaw = 0, math.pi - elseif rot == 6 then - pitch, yaw = math.pi/2, 0 - elseif rot == 7 then - pitch, yaw = -math.pi/2, math.pi - end - else - if rot == 1 then - pitch, yaw = math.pi, math.pi - elseif rot == 2 then - pitch, yaw = math.pi/2, math.pi/2 - elseif rot == 3 then - pitch, yaw = math.pi/2, -math.pi/2 - elseif rot == 4 then - pitch, yaw = math.pi/2, math.pi - elseif rot == 5 then - pitch, yaw = math.pi/2, 0 - elseif rot == 6 then - pitch, yaw = math.pi, -math.pi/2 - elseif rot == 7 then - pitch, yaw = 0, -math.pi/2 - end - end - if def.drawtype == "signlike" then - pitch = pitch - math.pi/2 - if rot == 0 then - yaw = yaw + math.pi/2 - elseif rot == 1 then - yaw = yaw - math.pi/2 - elseif rot == 6 then - yaw = yaw - math.pi/2 - pitch = pitch + math.pi - elseif rot == 7 then - yaw = yaw + math.pi/2 - pitch = pitch + math.pi - end - elseif def.drawtype == "mesh" or def.drawtype == "normal" or def.drawtype == "nodebox" then - if rot == 0 or rot == 1 then - roll = roll + math.pi - elseif rot == 6 or rot == 7 then - if def.drawtype ~= "normal" then - roll = roll - math.pi/2 - end - else - yaw = yaw + math.pi - end - end - self.object:set_rotation({x=pitch, y=yaw, z=roll}) - elseif (def.drawtype == "mesh" and def.paramtype2 == "degrotate") then - local p2 = (node.param2 - (def.place_param2 or 0)) % 240 - local yaw = (p2 / 240) * (math.pi * 2) - self.object:set_yaw(yaw) - elseif (def.drawtype == "mesh" and def.paramtype2 == "colordegrotate") then - local p2 = (node.param2 % 32 - (def.place_param2 or 0) % 32) % 24 - local yaw = (p2 / 24) * (math.pi * 2) - self.object:set_yaw(yaw) - end - end end, get_staticdata = function(self) diff --git a/doc/lua_api.md b/doc/lua_api.md index e45fa3c4e9602..8b844c01cd45b 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -1524,7 +1524,7 @@ Look for examples in `games/devtest` or `games/minetest_game`. * `allfaces` * Often used for partially-transparent nodes. * External sides of textures, and unlike other drawtypes, the external sides - of other blocks, are visible from the inside. + of other nodes, are visible from the inside. * `allfaces_optional` * Often used for leaves nodes. * This switches between `normal`, `glasslike` and `allfaces` according to @@ -9199,7 +9199,7 @@ Player properties need to be saved manually. -- Clients older than 5.9.0 interpret `pointable = "blocking"` as `pointable = true`. -- Can be overridden by the `pointabilities` of the held item. - visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item", + visual = "", -- "cube" is a node-sized cube. -- "sprite" is a flat texture always facing the player. -- "upright_sprite" is a vertical flat texture. @@ -9221,6 +9221,8 @@ Player properties need to be saved manually. -- Wielditems are scaled a bit. If you want a wielditem to appear -- to be as large as a node, use `0.667` in `visual_size` -- "item" is similar to "wielditem" but ignores the 'wield_image' parameter. + -- "node" looks exactly like a node in-world (supported since 5.11.0) + -- Note that visual effects like waving or liquid reflections will not work. visual_size = {x = 1, y = 1, z = 1}, -- Multipliers for the visual size. If `z` is not specified, `x` will be used @@ -9240,11 +9242,15 @@ Player properties need to be saved manually. colors = {}, -- Number of required colors depends on visual + node = {name = "ignore", param1=0, param2=0}, + -- Node to show when using the "node" visual + use_texture_alpha = false, -- Use texture's alpha channel. -- Excludes "upright_sprite" and "wielditem". -- Note: currently causes visual issues when viewed through other -- semi-transparent materials such as water. + -- Note: ignored for "item", "wielditem" and "node" visual. spritediv = {x = 1, y = 1}, -- Used with spritesheet textures for animation and/or frame selection @@ -9284,6 +9290,7 @@ Player properties need to be saved manually. backface_culling = true, -- Set to false to disable backface_culling for model + -- Note: only used by "mesh" and "cube" visual glow = 0, -- Add this much extra lighting when calculating texture color. @@ -9319,6 +9326,7 @@ Player properties need to be saved manually. shaded = true, -- Setting this to 'false' disables diffuse lighting of entity + -- Note: ignored for "item", "wielditem" and "node" visual show_on_minimap = false, -- Defaults to true for players, false for other entities. diff --git a/games/devtest/mods/testentities/visuals.lua b/games/devtest/mods/testentities/visuals.lua index 6bb1b828225bd..dfbf655ea75ba 100644 --- a/games/devtest/mods/testentities/visuals.lua +++ b/games/devtest/mods/testentities/visuals.lua @@ -66,6 +66,15 @@ core.register_entity("testentities:mesh_unshaded", { }, }) +core.register_entity("testentities:node", { + initial_properties = { + visual = "node", + node = { name = "stairs:stair_stone" }, + }, +}) + +-- More complex meshes + core.register_entity("testentities:sam", { initial_properties = { visual = "mesh", diff --git a/irr/include/IMeshSceneNode.h b/irr/include/IMeshSceneNode.h index 89b26dce15035..1fb0054054eb5 100644 --- a/irr/include/IMeshSceneNode.h +++ b/irr/include/IMeshSceneNode.h @@ -34,16 +34,16 @@ class IMeshSceneNode : public ISceneNode /** \return Pointer to mesh which is displayed by this node. */ virtual IMesh *getMesh(void) = 0; - //! Sets if the scene node should not copy the materials of the mesh but use them in a read only style. + //! 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 causing all mesh scene nodes referencing this mesh to change, too. - \param readonly Flag if the materials shall be read-only. */ - virtual void setReadOnlyMaterials(bool readonly) = 0; + \param shared Flag if the materials shall be shared. */ + virtual void setSharedMaterials(bool shared) = 0; - //! Check if the scene node should not copy the materials of the mesh but use them in a read only style - /** This flag can be set by setReadOnlyMaterials(). - \return Whether the materials are read-only. */ - virtual bool isReadOnlyMaterials() const = 0; + //! Check if the scene node does not copy the materials of the mesh but uses them directly. + /** This flag can be set by setSharedMaterials(). + \return Whether the materials are shared. */ + virtual bool isSharedMaterials() const = 0; }; } // end namespace scene diff --git a/irr/src/CMeshSceneNode.cpp b/irr/src/CMeshSceneNode.cpp index 89220cdc7052f..4f0a43212f54f 100644 --- a/irr/src/CMeshSceneNode.cpp +++ b/irr/src/CMeshSceneNode.cpp @@ -21,7 +21,7 @@ CMeshSceneNode::CMeshSceneNode(IMesh *mesh, ISceneNode *parent, ISceneManager *m const core::vector3df &scale) : IMeshSceneNode(parent, mgr, id, position, rotation, scale), Mesh(0), - PassCount(0), ReadOnlyMaterials(false) + PassCount(0), SharedMaterials(false) { setMesh(mesh); } @@ -49,9 +49,9 @@ void CMeshSceneNode::OnRegisterSceneNode() int solidCount = 0; // count transparent and solid materials in this scene node - const u32 numMaterials = ReadOnlyMaterials ? Mesh->getMeshBufferCount() : Materials.size(); + const u32 numMaterials = SharedMaterials ? Mesh->getMeshBufferCount() : Materials.size(); for (u32 i = 0; i < numMaterials; ++i) { - const video::SMaterial &material = ReadOnlyMaterials ? Mesh->getMeshBuffer(i)->getMaterial() : Materials[i]; + const auto &material = SharedMaterials ? Mesh->getMeshBuffer(i)->getMaterial() : Materials[i]; if (driver->needsTransparentRenderPass(material)) ++transparentCount; @@ -93,7 +93,7 @@ void CMeshSceneNode::render() for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i) { scene::IMeshBuffer *mb = Mesh->getMeshBuffer(i); if (mb) { - const video::SMaterial &material = ReadOnlyMaterials ? mb->getMaterial() : Materials[i]; + const auto &material = SharedMaterials ? mb->getMaterial() : Materials[i]; const bool transparent = driver->needsTransparentRenderPass(material); @@ -164,14 +164,10 @@ const core::aabbox3d &CMeshSceneNode::getBoundingBox() const //! returns the material based on the zero based index i. To get the amount //! of materials used by this scene node, use getMaterialCount(). -//! This function is needed for inserting the node into the scene hierarchy on a -//! optimal position for minimizing renderstate changes, but can also be used -//! to directly modify the material of a scene node. video::SMaterial &CMeshSceneNode::getMaterial(u32 i) { - if (Mesh && ReadOnlyMaterials && i < Mesh->getMeshBufferCount()) { - ReadOnlyMaterial = Mesh->getMeshBuffer(i)->getMaterial(); - return ReadOnlyMaterial; + if (Mesh && SharedMaterials && i < Mesh->getMeshBufferCount()) { + return Mesh->getMeshBuffer(i)->getMaterial(); } if (i >= Materials.size()) @@ -183,7 +179,7 @@ video::SMaterial &CMeshSceneNode::getMaterial(u32 i) //! returns amount of materials used by this scene node. u32 CMeshSceneNode::getMaterialCount() const { - if (Mesh && ReadOnlyMaterials) + if (Mesh && SharedMaterials) return Mesh->getMeshBufferCount(); return Materials.size(); @@ -206,9 +202,10 @@ void CMeshSceneNode::copyMaterials() { Materials.clear(); - if (Mesh) { + if (Mesh && !SharedMaterials) { video::SMaterial mat; + Materials.reserve(Mesh->getMeshBufferCount()); for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i) { IMeshBuffer *mb = Mesh->getMeshBuffer(i); if (mb) @@ -222,15 +219,18 @@ void CMeshSceneNode::copyMaterials() //! Sets if the scene node should not copy the materials of the mesh but use them in a read only style. /* In this way it is possible to change the materials a mesh causing all mesh scene nodes referencing this mesh to change too. */ -void CMeshSceneNode::setReadOnlyMaterials(bool readonly) +void CMeshSceneNode::setSharedMaterials(bool shared) { - ReadOnlyMaterials = readonly; + if (SharedMaterials != shared) { + SharedMaterials = shared; + copyMaterials(); + } } //! Returns if the scene node should not copy the materials of the mesh but use them in a read only style -bool CMeshSceneNode::isReadOnlyMaterials() const +bool CMeshSceneNode::isSharedMaterials() const { - return ReadOnlyMaterials; + return SharedMaterials; } //! Creates a clone of this scene node and its children. @@ -245,7 +245,7 @@ ISceneNode *CMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManag newManager, ID, RelativeTranslation, RelativeRotation, RelativeScale); nb->cloneMembers(this, newManager); - nb->ReadOnlyMaterials = ReadOnlyMaterials; + nb->SharedMaterials = SharedMaterials; nb->Materials = Materials; if (newParent) diff --git a/irr/src/CMeshSceneNode.h b/irr/src/CMeshSceneNode.h index 6791a348447f1..20c7146ea76a7 100644 --- a/irr/src/CMeshSceneNode.h +++ b/irr/src/CMeshSceneNode.h @@ -52,13 +52,16 @@ class CMeshSceneNode : public IMeshSceneNode //! Returns the current mesh IMesh *getMesh(void) override { return Mesh; } - //! Sets if the scene node should not copy the materials of the mesh but use them in a read only style. - /* In this way it is possible to change the materials a mesh causing all mesh scene nodes - referencing this mesh to change too. */ - void setReadOnlyMaterials(bool readonly) override; + //! 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 + causing all mesh scene nodes referencing this mesh to change, too. + \param shared Flag if the materials shall be shared. */ + void setSharedMaterials(bool shared) override; - //! Returns if the scene node should not copy the materials of the mesh but use them in a read only style - bool isReadOnlyMaterials() const override; + //! Check if the scene node does not copy the materials of the mesh but uses them directly. + /** This flag can be set by setSharedMaterials(). + \return Whether the materials are shared. */ + bool isSharedMaterials() const override; //! Creates a clone of this scene node and its children. ISceneNode *clone(ISceneNode *newParent = 0, ISceneManager *newManager = 0) override; @@ -71,14 +74,13 @@ class CMeshSceneNode : public IMeshSceneNode protected: void copyMaterials(); - core::array Materials; + std::vector Materials; core::aabbox3d Box{{0, 0, 0}}; - video::SMaterial ReadOnlyMaterial; IMesh *Mesh; s32 PassCount; - bool ReadOnlyMaterials; + bool SharedMaterials; }; } // end namespace scene diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 5c044ddeffdbc..00a21659b166d 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -12,6 +12,8 @@ #include "client/sound.h" #include "client/texturesource.h" #include "client/mapblock_mesh.h" +#include "client/content_mapblock.h" +#include "client/meshgen/collector.h" #include "util/basic_macros.h" #include "util/numeric.h" #include "util/serialize.h" @@ -177,6 +179,60 @@ static void setColorParam(scene::ISceneNode *node, video::SColor color) node->getMaterial(i).ColorParam = color; } +static scene::SMesh *generateNodeMesh(Client *client, MapNode n, + std::vector &animation) +{ + auto *ndef = client->ndef(); + auto *shdsrc = client->getShaderSource(); + + MeshCollector collector(v3f(0), v3f()); + { + MeshMakeData mmd(ndef, 1, MeshGrid{1}); + n.setParam1(0xff); + mmd.fillSingleNode(n); + MapblockMeshGenerator(&mmd, &collector).generate(); + } + + auto mesh = make_irr(); + animation.clear(); + for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { + for (PreMeshBuffer &p : collector.prebuffers[layer]) { + // reset the pre-computed light data stored in the vertex color, + // since we do that ourselves via updateLight(). + for (auto &v : p.vertices) + v.Color.set(0xFFFFFFFF); + // but still apply the tile color + p.applyTileColor(); + + if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { + const FrameSpec &frame = (*p.layer.frames)[0]; + p.layer.texture = frame.texture; + + animation.emplace_back(MeshAnimationInfo{mesh->getMeshBufferCount(), 0, p.layer}); + } + + auto buf = make_irr(); + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], p.indices.size()); + + // Set up material + auto &mat = buf->Material; + mat.setTexture(0, p.layer.texture); + u32 shader_id = shdsrc->getShader("object_shader", p.layer.material_type, NDT_NORMAL); + mat.MaterialType = shdsrc->getShaderInfo(shader_id).material; + if (layer == 1) { + mat.PolygonOffsetSlopeScale = -1; + mat.PolygonOffsetDepthBias = -1; + } + p.layer.applyMaterialOptionsWithShaders(mat); + + mesh->addMeshBuffer(buf.get()); + } + } + mesh->recalculateBoundingBox(); + return mesh.release(); +} + /* TestCAO */ @@ -569,6 +625,8 @@ void GenericCAO::removeFromScene(bool permanent) m_spritenode = nullptr; } + m_meshnode_animation.clear(); + if (m_matrixnode) { m_matrixnode->remove(); m_matrixnode->drop(); @@ -599,8 +657,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl; - m_material_type_param = 0.5f; // May cut off alpha < 128 depending on m_material_type - + if (m_prop.visual != "node" && m_prop.visual != "wielditem" && m_prop.visual != "item") { IShaderSource *shader_source = m_client->getShaderSource(); MaterialType material_type; @@ -614,15 +671,17 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) u32 shader_id = shader_source->getShader("object_shader", material_type, NDT_NORMAL); m_material_type = shader_source->getShaderInfo(shader_id).material; + } else { + // Not used, so make sure it's not valid + m_material_type = EMT_INVALID; } - auto grabMatrixNode = [this] { - m_matrixnode = m_smgr->addDummyTransformationSceneNode(); - m_matrixnode->grab(); - }; + m_matrixnode = m_smgr->addDummyTransformationSceneNode(); + m_matrixnode->grab(); auto setMaterial = [this] (video::SMaterial &mat) { - mat.MaterialType = m_material_type; + if (m_material_type != EMT_INVALID) + mat.MaterialType = m_material_type; mat.FogEnable = true; mat.forEachTexture([] (auto &tex) { tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; @@ -635,7 +694,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) }; if (m_prop.visual == "sprite") { - grabMatrixNode(); m_spritenode = m_smgr->addBillboardSceneNode( m_matrixnode, v2f(1, 1), v3f(0,0,0), -1); m_spritenode->grab(); @@ -655,7 +713,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) txs, tys, 0, 0); } } else if (m_prop.visual == "upright_sprite") { - grabMatrixNode(); auto mesh = make_irr(); f32 dx = BS * m_prop.visual_size.X / 2; f32 dy = BS * m_prop.visual_size.Y / 2; @@ -697,7 +754,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_meshnode = m_smgr->addMeshSceneNode(mesh.get(), m_matrixnode); m_meshnode->grab(); } else if (m_prop.visual == "cube") { - grabMatrixNode(); scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS)); m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode); m_meshnode->grab(); @@ -711,7 +767,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) mat.BackfaceCulling = m_prop.backface_culling; }); } else if (m_prop.visual == "mesh") { - grabMatrixNode(); scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true); if (mesh) { if (!checkMeshNormals(mesh)) { @@ -738,7 +793,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) } else errorstream<<"GenericCAO::addToScene(): Could not load mesh "<setScale(m_prop.visual_size / 2.0f); + } else if (m_prop.visual == "node") { + auto *mesh = generateNodeMesh(m_client, m_prop.node, m_meshnode_animation); + assert(mesh); + + m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode); + m_meshnode->setSharedMaterials(true); + m_meshnode->grab(); + mesh->drop(); + + m_meshnode->setScale(m_prop.visual_size); + + setSceneNodeMaterials(m_meshnode); } else { infostream<<"GenericCAO::addToScene(): \""<setParent(m_matrixnode); + node->setParent(m_matrixnode); if (auto shadow = RenderingEngine::get_shadow_renderer()) shadow->addNodeToShadowList(node); @@ -858,8 +923,7 @@ void GenericCAO::updateLight(u32 day_night_ratio) if (!pos_ok) light_at_pos = LIGHT_SUN; - // Initialize with full alpha, otherwise entity won't be visible - video::SColor light{0xFFFFFFFF}; + video::SColor light; // Encode light into color, adding a small boost // based on the entity glow. @@ -962,6 +1026,7 @@ void GenericCAO::updateNodePos() scene::ISceneNode *node = getSceneNode(); if (node) { + assert(m_matrixnode); v3s16 camera_offset = m_env->getCameraOffset(); v3f pos = pos_translator.val_current - intToFloat(camera_offset, BS); @@ -1150,7 +1215,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) m_anim_frame = 0; } - updateTexturePos(); + updateTextureAnim(); if(m_reset_textures_timer >= 0) { @@ -1211,7 +1276,7 @@ static void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u buf->setDirty(scene::EBT_VERTEX); } -void GenericCAO::updateTexturePos() +void GenericCAO::updateTextureAnim() { if(m_spritenode) { @@ -1276,6 +1341,23 @@ void GenericCAO::updateTexturePos() auto mesh = m_meshnode->getMesh(); setMeshBufferTextureCoords(mesh->getMeshBuffer(0), t, 4); setMeshBufferTextureCoords(mesh->getMeshBuffer(1), t, 4); + } else if (m_prop.visual == "node") { + // same calculation as MapBlockMesh::animate() with a global timer + const float time = m_client->getAnimationTime(); + for (auto &it : m_meshnode_animation) { + const TileLayer &tile = it.tile; + int frameno = (int)(time * 1000 / tile.animation_frame_length_ms) + % tile.animation_frame_count; + + if (frameno == it.frame) + continue; + it.frame = frameno; + + auto *buf = m_meshnode->getMesh()->getMeshBuffer(it.i); + + const FrameSpec &frame = (*tile.frames)[frameno]; + buf->getMaterial().setTexture(0, frame.texture); + } } } } @@ -1301,7 +1383,6 @@ void GenericCAO::updateTextures(std::string mod) video::SMaterial &material = m_spritenode->getMaterial(0); material.MaterialType = m_material_type; - material.MaterialTypeParam = m_material_type_param; material.setTexture(0, tsrc->getTextureForMesh(texturestring)); material.forEachTexture([=] (auto &tex) { @@ -1330,7 +1411,6 @@ void GenericCAO::updateTextures(std::string mod) // Set material flags and texture video::SMaterial &material = m_animated_meshnode->getMaterial(i); material.MaterialType = m_material_type; - material.MaterialTypeParam = m_material_type_param; material.TextureLayers[0].Texture = texture; material.BackfaceCulling = m_prop.backface_culling; @@ -1362,7 +1442,6 @@ void GenericCAO::updateTextures(std::string mod) // Set material flags and texture video::SMaterial &material = m_meshnode->getMaterial(i); material.MaterialType = m_material_type; - material.MaterialTypeParam = m_material_type_param; material.setTexture(0, tsrc->getTextureForMesh(texturestring)); material.getTextureMatrix(0).makeIdentity(); @@ -1529,7 +1608,7 @@ bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const /* Visuals do not need to be expired for: * - nametag props: handled by updateNametag() * - textures: handled by updateTextures() - * - sprite props: handled by updateTexturePos() + * - sprite props: handled by updateTextureAnim() * - glow: handled by updateLight() * - any other properties that do not change appearance */ @@ -1539,9 +1618,10 @@ bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const // Ordered to compare primitive types before std::vectors return old.backface_culling != new_.backface_culling || old.is_visible != new_.is_visible || - old.mesh != new_.mesh || old.shaded != new_.shaded || old.use_texture_alpha != new_.use_texture_alpha || + old.node != new_.node || + old.mesh != new_.mesh || old.visual != new_.visual || old.visual_size != new_.visual_size || old.wield_item != new_.wield_item || @@ -1652,7 +1732,7 @@ void GenericCAO::processMessage(const std::string &data) m_anim_framelength = framelength; m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch; - updateTexturePos(); + updateTextureAnim(); } else if (cmd == AO_CMD_SET_PHYSICS_OVERRIDE) { float override_speed = readF32(is); float override_jump = readF32(is); diff --git a/src/client/content_cao.h b/src/client/content_cao.h index a6b9beeab6498..fe804eeab8a65 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -12,6 +12,7 @@ #include "clientobject.h" #include "constants.h" #include "itemgroup.h" +#include "client/tile.h" #include #include #include @@ -27,7 +28,7 @@ struct Nametag; struct MinimapMarker; /* - SmoothTranslator + SmoothTranslator and other helpers */ template @@ -60,9 +61,21 @@ struct SmoothTranslatorWrappedv3f : SmoothTranslator void translate(f32 dtime); }; +struct MeshAnimationInfo { + u32 i; /// index of mesh buffer + int frame; /// last animation frame + TileLayer tile; +}; + +/* + GenericCAO +*/ + class GenericCAO : public ClientActiveObject { private: + static constexpr auto EMT_INVALID = video::EMT_FORCE_32BIT; + // Only set at initialization std::string m_name = ""; bool m_is_player = false; @@ -73,6 +86,8 @@ class GenericCAO : public ClientActiveObject scene::ISceneManager *m_smgr = nullptr; Client *m_client = nullptr; aabb3f m_selection_box = aabb3f(-BS/3.,-BS/3.,-BS/3., BS/3.,BS/3.,BS/3.); + + // Visuals scene::IMeshSceneNode *m_meshnode = nullptr; scene::IAnimatedMeshSceneNode *m_animated_meshnode = nullptr; WieldMeshSceneNode *m_wield_meshnode = nullptr; @@ -80,6 +95,15 @@ class GenericCAO : public ClientActiveObject scene::IDummyTransformationSceneNode *m_matrixnode = nullptr; Nametag *m_nametag = nullptr; MinimapMarker *m_marker = nullptr; + bool m_visuals_expired = false; + video::SColor m_last_light = video::SColor(0xFFFFFFFF); + bool m_is_visible = false; + std::vector m_meshnode_animation; + + // Material + video::E_MATERIAL_TYPE m_material_type = EMT_INVALID; + + // Movement v3f m_position = v3f(0.0f, 10.0f * BS, 0); v3f m_velocity; v3f m_acceleration; @@ -87,18 +111,25 @@ class GenericCAO : public ClientActiveObject u16 m_hp = 1; SmoothTranslator pos_translator; SmoothTranslatorWrappedv3f rot_translator; - // Spritesheet/animation stuff + + // Spritesheet stuff v2f m_tx_size = v2f(1,1); v2s16 m_tx_basepos; bool m_initial_tx_basepos_set = false; bool m_tx_select_horiz_by_yawpitch = false; + bool m_animation_loop = true; v2f m_animation_range; float m_animation_speed = 15.0f; float m_animation_blend = 0.0f; - bool m_animation_loop = true; + int m_anim_frame = 0; + int m_anim_num_frames = 1; + float m_anim_framelength = 0.2f; + float m_anim_timer = 0.0f; + // stores position and rotation for each bone name BoneOverrideMap m_bone_override; + // Attachments object_t m_attachment_parent_id = 0; std::unordered_set m_attachment_child_ids; std::string m_attachment_bone = ""; @@ -107,23 +138,13 @@ class GenericCAO : public ClientActiveObject bool m_attached_to_local = false; bool m_force_visible = false; - int m_anim_frame = 0; - int m_anim_num_frames = 1; - float m_anim_framelength = 0.2f; - float m_anim_timer = 0.0f; ItemGroupList m_armor_groups; float m_reset_textures_timer = -1.0f; // stores texture modifier before punch update std::string m_previous_texture_modifier = ""; // last applied texture modifier std::string m_current_texture_modifier = ""; - bool m_visuals_expired = false; float m_step_distance_counter = 0.0f; - video::SColor m_last_light = video::SColor(0xFFFFFFFF); - bool m_is_visible = false; - // Material - video::E_MATERIAL_TYPE m_material_type; - f32 m_material_type_param; bool visualExpiryRequired(const ObjectProperties &newprops) const; @@ -255,7 +276,7 @@ class GenericCAO : public ClientActiveObject void step(float dtime, ClientEnvironment *env) override; - void updateTexturePos(); + void updateTextureAnim(); // ffs this HAS TO BE a string copy! See #5739 if you think otherwise // Reason: updateTextures(m_previous_texture_modifier); diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 6eabe4c2d3a91..b9e931bf6fcf6 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -423,20 +423,6 @@ void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *dat tile.rotation = tile.world_aligned ? TileRotation::None : dir_to_tile[facedir][dir_i].rotation; } -static void applyTileColor(PreMeshBuffer &pmb) -{ - video::SColor tc = pmb.layer.color; - if (tc == video::SColor(0xFFFFFFFF)) - return; - for (video::S3DVertex &vertex : pmb.vertices) { - video::SColor *c = &vertex.Color; - c->set(c->getAlpha(), - c->getRed() * tc.getRed() / 255, - c->getGreen() * tc.getGreen() / 255, - c->getBlue() * tc.getBlue() / 255); - } -} - /* MapBlockBspTree */ @@ -665,7 +651,7 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data): { PreMeshBuffer &p = collector.prebuffers[layer][i]; - applyTileColor(p); + p.applyTileColor(); // Generate animation data // - Cracks diff --git a/src/client/meshgen/collector.h b/src/client/meshgen/collector.h index 693e2be049c54..f1f8a1481d072 100644 --- a/src/client/meshgen/collector.h +++ b/src/client/meshgen/collector.h @@ -18,6 +18,21 @@ struct PreMeshBuffer PreMeshBuffer() = default; explicit PreMeshBuffer(const TileLayer &layer) : layer(layer) {} + + /// @brief Colorizes vertices as indicated by tile layer + void applyTileColor() + { + video::SColor tc = layer.color; + if (tc == video::SColor(0xFFFFFFFF)) + return; + for (auto &vertex : vertices) { + video::SColor *c = &vertex.Color; + c->set(c->getAlpha(), + c->getRed() * tc.getRed() / 255U, + c->getGreen() * tc.getGreen() / 255U, + c->getBlue() * tc.getBlue() / 255U); + } + } }; struct MeshCollector diff --git a/src/client/tile.h b/src/client/tile.h index 7f4805e84829c..a337f13813a2f 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -9,7 +9,7 @@ #include #include -enum MaterialType{ +enum MaterialType : u8 { TILE_MATERIAL_BASIC, TILE_MATERIAL_ALPHA, TILE_MATERIAL_LIQUID_TRANSPARENT, @@ -98,8 +98,9 @@ struct TileLayer case TILE_MATERIAL_LIQUID_TRANSPARENT: case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: return true; + default: + return false; } - return false; } // Ordered for size, please do not reorder @@ -113,13 +114,14 @@ struct TileLayer u16 animation_frame_length_ms = 0; u16 animation_frame_count = 1; - u8 material_type = TILE_MATERIAL_BASIC; + MaterialType material_type = TILE_MATERIAL_BASIC; u8 material_flags = //0 // <- DEBUG, Use the one below MATERIAL_FLAG_BACKFACE_CULLING | MATERIAL_FLAG_TILEABLE_HORIZONTAL| MATERIAL_FLAG_TILEABLE_VERTICAL; + /// @note not owned by this struct std::vector *frames = nullptr; /*! diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index f03de2a5bdc7e..730afb0c7f739 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -201,7 +201,6 @@ WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id): // Create the child scene node scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube(); m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1); - m_meshnode->setReadOnlyMaterials(false); m_meshnode->setVisible(false); dummymesh->drop(); // m_meshnode grabbed it diff --git a/src/mapnode.h b/src/mapnode.h index a909757bcf935..c0f1f4f105716 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -145,7 +145,7 @@ struct alignas(u32) MapNode MapNode() = default; - MapNode(content_t content, u8 a_param1=0, u8 a_param2=0) noexcept + constexpr MapNode(content_t content, u8 a_param1=0, u8 a_param2=0) noexcept : param0(content), param1(a_param1), param2(a_param2) @@ -157,6 +157,10 @@ struct alignas(u32) MapNode && param1 == other.param1 && param2 == other.param2); } + bool operator!=(const MapNode &other) const noexcept + { + return !(*this == other); + } // To be used everywhere content_t getContent() const noexcept diff --git a/src/nodedef.cpp b/src/nodedef.cpp index a8273850522d0..6994464edbd03 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -669,7 +669,7 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version) #if CHECK_CLIENT_BUILD() static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, const TileSpec &tile, const TileDef &tiledef, video::SColor color, - u8 material_type, u32 shader_id, bool backface_culling, + MaterialType material_type, u32 shader_id, bool backface_culling, const TextureSettings &tsettings) { layer->shader_id = shader_id; diff --git a/src/object_properties.cpp b/src/object_properties.cpp index 63a0ef01bc438..0c8627899b61c 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -64,6 +64,8 @@ std::string ObjectProperties::dump() const os << ", static_save=" << static_save; os << ", eye_height=" << eye_height; os << ", zoom_fov=" << zoom_fov; + os << ", node=(" << (int)node.getContent() << ", " << (int)node.getParam1() + << ", " << (int)node.getParam2() << ")"; os << ", use_texture_alpha=" << use_texture_alpha; os << ", damage_texture_modifier=" << damage_texture_modifier; os << ", shaded=" << shaded; @@ -80,8 +82,8 @@ static auto tie(const ObjectProperties &o) o.nametag_color, o.nametag_bgcolor, o.spritediv, o.initial_sprite_basepos, o.stepheight, o.automatic_rotate, o.automatic_face_movement_dir_offset, o.automatic_face_movement_max_rotation_per_sec, o.eye_height, o.zoom_fov, - o.hp_max, o.breath_max, o.glow, o.pointable, o.physical, o.collideWithObjects, - o.rotate_selectionbox, o.is_visible, o.makes_footstep_sound, + o.node, o.hp_max, o.breath_max, o.glow, o.pointable, o.physical, + o.collideWithObjects, o.rotate_selectionbox, o.is_visible, o.makes_footstep_sound, o.automatic_face_movement_dir, o.backface_culling, o.static_save, o.use_texture_alpha, o.shaded, o.show_on_minimap ); @@ -171,6 +173,7 @@ void ObjectProperties::serialize(std::ostream &os) const writeU8(os, shaded); writeU8(os, show_on_minimap); + // use special value to tell apart nil, fully transparent and other colors if (!nametag_bgcolor) writeARGB8(os, NULL_BGCOLOR); else if (nametag_bgcolor.value().getAlpha() == 0) @@ -179,8 +182,31 @@ void ObjectProperties::serialize(std::ostream &os) const writeARGB8(os, nametag_bgcolor.value()); writeU8(os, rotate_selectionbox); + writeU16(os, node.getContent()); + writeU8(os, node.getParam1()); + writeU8(os, node.getParam2()); + // Add stuff only at the bottom. - // Never remove anything, because we don't want new versions of this + // Never remove anything, because we don't want new versions of this! +} + +namespace { + // Type-safe wrapper for bools as u8 + inline bool readBool(std::istream &is) + { + return readU8(is) != 0; + } + + // Wrapper for primitive reading functions that don't throw (awful) + template + bool tryRead(T& val, std::istream& is) + { + T tmp = reader(is); + if (is.eof()) + return false; + val = tmp; + return true; + } } void ObjectProperties::deSerialize(std::istream &is) @@ -230,26 +256,32 @@ void ObjectProperties::deSerialize(std::istream &is) eye_height = readF32(is); zoom_fov = readF32(is); use_texture_alpha = readU8(is); + try { damage_texture_modifier = deSerializeString16(is); - u8 tmp = readU8(is); - if (is.eof()) - return; - shaded = tmp; - tmp = readU8(is); - if (is.eof()) - return; - show_on_minimap = tmp; + } catch (SerializationError &e) { + return; + } - auto bgcolor = readARGB8(is); - if (bgcolor != NULL_BGCOLOR) - nametag_bgcolor = bgcolor; - else - nametag_bgcolor = std::nullopt; + if (!tryRead(shaded, is)) + return; - tmp = readU8(is); - if (is.eof()) - return; - rotate_selectionbox = tmp; - } catch (SerializationError &e) {} + if (!tryRead(show_on_minimap, is)) + return; + + auto bgcolor = readARGB8(is); + if (bgcolor != NULL_BGCOLOR) + nametag_bgcolor = bgcolor; + else + nametag_bgcolor = std::nullopt; + + if (!tryRead(rotate_selectionbox, is)) + return; + + if (!tryRead(node.param0, is)) + return; + node.param1 = readU8(is); + node.param2 = readU8(is); + + // Add new properties down here and remember to use either tryRead<> or a try-catch. } diff --git a/src/object_properties.h b/src/object_properties.h index d97b4997eb369..5392e2a27c5f5 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -10,6 +10,7 @@ #include #include #include "util/pointabilities.h" +#include "mapnode.h" struct ObjectProperties { @@ -39,6 +40,7 @@ struct ObjectProperties f32 automatic_face_movement_max_rotation_per_sec = -1.0f; float eye_height = 1.625f; float zoom_fov = 0.0f; + MapNode node = MapNode(CONTENT_IGNORE); u16 hp_max = 1; u16 breath_max = 0; s8 glow = 0; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index e343d50db2af4..f0095b4e69fba 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -277,11 +277,13 @@ const std::array object_property_keys = { "shaded", "damage_texture_modifier", "show_on_minimap", + // "node" is intentionally not here as it's gated behind `fallback` below! }; /******************************************************************************/ void read_object_properties(lua_State *L, int index, - ServerActiveObject *sao, ObjectProperties *prop, IItemDefManager *idef) + ServerActiveObject *sao, ObjectProperties *prop, IItemDefManager *idef, + bool fallback) { if(index < 0) index = lua_gettop(L) + 1 + index; @@ -382,6 +384,16 @@ void read_object_properties(lua_State *L, int index, } lua_pop(L, 1); + // This hack exists because the name 'node' easily collides with mods own + // usage (or in this case literally builtin/game/falling.lua). + if (!fallback) { + lua_getfield(L, -1, "node"); + if (lua_istable(L, -1)) { + prop->node = readnode(L, -1); + } + lua_pop(L, 1); + } + lua_getfield(L, -1, "spritediv"); if(lua_istable(L, -1)) prop->spritediv = read_v2s16(L, -1); @@ -496,6 +508,8 @@ void push_object_properties(lua_State *L, const ObjectProperties *prop) } lua_setfield(L, -2, "colors"); + pushnode(L, prop->node); + lua_setfield(L, -2, "node"); push_v2s16(L, prop->spritediv); lua_setfield(L, -2, "spritediv"); push_v2s16(L, prop->initial_sprite_basepos); diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 3c8780c1ac8d3..70596d1be9531 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -110,10 +110,12 @@ void push_item_definition (lua_State *L, void push_item_definition_full (lua_State *L, const ItemDefinition &i); +/// @param fallback set to true if reading from bare entity table (not initial_properties) void read_object_properties (lua_State *L, int index, ServerActiveObject *sao, ObjectProperties *prop, - IItemDefManager *idef); + IItemDefManager *idef, + bool fallback = false); void push_object_properties (lua_State *L, const ObjectProperties *prop); diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp index c1b7244dfd799..fd4d9fed66619 100644 --- a/src/script/cpp_api/s_entity.cpp +++ b/src/script/cpp_api/s_entity.cpp @@ -197,13 +197,17 @@ void ScriptApiEntity::luaentity_GetProperties(u16 id, // Set default values that differ from ObjectProperties defaults prop->hp_max = 10; + auto *idef = getServer()->idef(); + // Deprecated: read object properties directly + // TODO: this should be changed to not read the legacy place + // if `initial_properties` exists! logDeprecationForExistingProperties(L, -1, entity_name); - read_object_properties(L, -1, self, prop, getServer()->idef()); + read_object_properties(L, -1, self, prop, idef, true); // Read initial_properties lua_getfield(L, -1, "initial_properties"); - read_object_properties(L, -1, self, prop, getServer()->idef()); + read_object_properties(L, -1, self, prop, idef); lua_pop(L, 1); }