1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Draw node animation for items (#15930)

This commit is contained in:
cx384 2025-04-04 18:47:11 +02:00 committed by GitHub
parent 1db5a2f950
commit a6d4cd7c15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 117 additions and 74 deletions

View file

@ -134,7 +134,8 @@ void Camera::step(f32 dtime)
if (m_wield_change_timer >= 0 && was_under_zero) { if (m_wield_change_timer >= 0 && was_under_zero) {
m_wieldnode->setItem(m_wield_item_next, m_client); m_wieldnode->setItem(m_wield_item_next, m_client);
m_wieldnode->setNodeLightColor(m_player_light_color); m_wieldnode->setLightColorAndAnimation(m_player_light_color,
m_client->getAnimationTime());
} }
if (m_view_bobbing_state != 0) if (m_view_bobbing_state != 0)
@ -537,7 +538,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
m_wieldnode->setRotation(wield_rotation); m_wieldnode->setRotation(wield_rotation);
m_player_light_color = player->light_color; m_player_light_color = player->light_color;
m_wieldnode->setNodeLightColor(m_player_light_color); m_wieldnode->setLightColorAndAnimation(m_player_light_color,
m_client->getAnimationTime());
// Set render distance // Set render distance
updateViewingRange(); updateViewingRange();

View file

@ -809,7 +809,8 @@ void GenericCAO::setNodeLight(const video::SColor &light_color)
{ {
if (m_prop.visual == OBJECTVISUAL_WIELDITEM || m_prop.visual == OBJECTVISUAL_ITEM) { if (m_prop.visual == OBJECTVISUAL_WIELDITEM || m_prop.visual == OBJECTVISUAL_ITEM) {
if (m_wield_meshnode) if (m_wield_meshnode)
m_wield_meshnode->setNodeLightColor(light_color); m_wield_meshnode->setLightColorAndAnimation(light_color,
m_client->getAnimationTime());
return; return;
} }

View file

@ -678,9 +678,7 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data):
// - Texture animation // - Texture animation
if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
// Add to MapBlockMesh in order to animate these tiles // Add to MapBlockMesh in order to animate these tiles
auto &info = m_animation_info[{layer, i}]; m_animation_info.emplace(std::make_pair(layer, i), AnimationInfo(p.layer));
info.tile = p.layer;
info.frame = 0;
// Replace tile texture with the first animation frame // Replace tile texture with the first animation frame
p.layer.texture = (*p.layer.frames)[0].texture; p.layer.texture = (*p.layer.frames)[0].texture;
} }
@ -763,6 +761,12 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
// Cracks // Cracks
if (crack != m_last_crack) { if (crack != m_last_crack) {
for (auto &crack_material : m_crack_materials) { for (auto &crack_material : m_crack_materials) {
// TODO crack on animated tiles does not work
auto anim_it = m_animation_info.find(crack_material.first);
if (anim_it != m_animation_info.end())
continue;
scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]-> scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
getMeshBuffer(crack_material.first.second); getMeshBuffer(crack_material.first.second);
@ -772,16 +776,6 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
video::ITexture *new_texture = video::ITexture *new_texture =
m_tsrc->getTextureForMesh(s, &new_texture_id); m_tsrc->getTextureForMesh(s, &new_texture_id);
buf->getMaterial().setTexture(0, new_texture); buf->getMaterial().setTexture(0, new_texture);
// If the current material is also animated, update animation info
auto anim_it = m_animation_info.find(crack_material.first);
if (anim_it != m_animation_info.end()) {
TileLayer &tile = anim_it->second.tile;
tile.texture = new_texture;
tile.texture_id = new_texture_id;
// force animation update
anim_it->second.frame = -1;
}
} }
m_last_crack = crack; m_last_crack = crack;
@ -789,20 +783,9 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
// Texture animation // Texture animation
for (auto &it : m_animation_info) { for (auto &it : m_animation_info) {
const TileLayer &tile = it.second.tile;
// Figure out current frame
int frameno = (int)(time * 1000 / tile.animation_frame_length_ms) %
tile.animation_frame_count;
// If frame doesn't change, skip
if (frameno == it.second.frame)
continue;
it.second.frame = frameno;
scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second); scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second);
video::SMaterial &material = buf->getMaterial();
const FrameSpec &frame = (*tile.frames)[frameno]; it.second.updateTexture(material, time);
buf->getMaterial().setTexture(0, frame.texture);
} }
return true; return true;

View file

@ -246,10 +246,6 @@ public:
} }
private: private:
struct AnimationInfo {
int frame; // last animation frame
TileLayer tile;
};
irr_ptr<scene::IMesh> m_mesh[MAX_TILE_LAYERS]; irr_ptr<scene::IMesh> m_mesh[MAX_TILE_LAYERS];
std::vector<MinimapMapblock*> m_minimap_mapblocks; std::vector<MinimapMapblock*> m_minimap_mapblocks;

View file

@ -3,6 +3,19 @@
// Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com> // Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
#include "tile.h" #include "tile.h"
#include <cassert>
void AnimationInfo::updateTexture(video::SMaterial &material, float animation_time)
{
// Figure out current frame
u16 frame = (u16)(animation_time * 1000 / m_frame_length_ms) % m_frame_count;
// Only adjust if frame changed
if (frame != m_frame) {
m_frame = frame;
assert(m_frame < m_frames->size());
material.setTexture(0, (*m_frames)[m_frame].texture);
}
};
void TileLayer::applyMaterialOptions(video::SMaterial &material, int layer) const void TileLayer::applyMaterialOptions(video::SMaterial &material, int layer) const
{ {

View file

@ -151,6 +151,28 @@ struct TileLayer
bool has_color = false; bool has_color = false;
}; };
// Stores information for drawing an animated tile
struct AnimationInfo {
AnimationInfo() = default;
AnimationInfo(const TileLayer &tile) :
m_frame_length_ms(tile.animation_frame_length_ms),
m_frame_count(tile.animation_frame_count),
m_frames(tile.frames)
{};
void updateTexture(video::SMaterial &material, float animation_time);
private:
u16 m_frame = 0; // last animation frame
u16 m_frame_length_ms = 0;
u16 m_frame_count = 1;
/// @note not owned by this struct
std::vector<FrameSpec> *m_frames = nullptr;
};
enum class TileRotation: u8 { enum class TileRotation: u8 {
None, None,
R90, R90,

View file

@ -30,6 +30,14 @@
#define MIN_EXTRUSION_MESH_RESOLUTION 16 #define MIN_EXTRUSION_MESH_RESOLUTION 16
#define MAX_EXTRUSION_MESH_RESOLUTION 512 #define MAX_EXTRUSION_MESH_RESOLUTION 512
ItemMeshBufferInfo::ItemMeshBufferInfo(const TileLayer &layer) :
override_color(layer.color),
override_color_set(layer.has_color),
animation_info((layer.material_flags & MATERIAL_FLAG_ANIMATION) ?
std::make_unique<AnimationInfo>(layer) :
nullptr)
{}
static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y)
{ {
const f32 r = 0.5; const f32 r = 0.5;
@ -285,7 +293,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename,
} }
static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
std::vector<ItemPartColor> *colors, const ContentFeatures &f) std::vector<ItemMeshBufferInfo> *buffer_info, const ContentFeatures &f)
{ {
n.setParam1(0xff); n.setParam1(0xff);
if (n.getParam2()) { if (n.getParam2()) {
@ -309,7 +317,7 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
MapblockMeshGenerator(&mmd, &collector).generate(); MapblockMeshGenerator(&mmd, &collector).generate();
} }
colors->clear(); buffer_info->clear();
scene::SMesh *mesh = new scene::SMesh(); scene::SMesh *mesh = new scene::SMesh();
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
auto &prebuffers = collector.prebuffers[layer]; auto &prebuffers = collector.prebuffers[layer];
@ -329,7 +337,7 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
p.layer.applyMaterialOptions(buf->Material, layer); p.layer.applyMaterialOptions(buf->Material, layer);
mesh->addMeshBuffer(buf.get()); mesh->addMeshBuffer(buf.get());
colors->emplace_back(p.layer.has_color, p.layer.color); buffer_info->emplace_back(p.layer);
} }
} }
mesh->recalculateBoundingBox(); mesh->recalculateBoundingBox();
@ -352,7 +360,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
m_material_type = shdrsrc->getShaderInfo(shader_id).material; m_material_type = shdrsrc->getShaderInfo(shader_id).material;
// Color-related // Color-related
m_colors.clear(); m_buffer_info.clear();
m_base_color = idef->getItemstackColor(item, client); m_base_color = idef->getItemstackColor(item, client);
const std::string wield_image = item.getWieldImage(idef); const std::string wield_image = item.getWieldImage(idef);
@ -361,11 +369,10 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
// If wield_image needs to be checked and is defined, it overrides everything else // If wield_image needs to be checked and is defined, it overrides everything else
if (!wield_image.empty() && check_wield_image) { if (!wield_image.empty() && check_wield_image) {
setExtruded(wield_image, wield_overlay, wield_scale, tsrc, setExtruded(wield_image, wield_overlay, wield_scale, tsrc, 1);
1); m_buffer_info.emplace_back();
m_colors.emplace_back();
// overlay is white, if present // overlay is white, if present
m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); m_buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF));
// initialize the color // initialize the color
setColor(video::SColor(0xFFFFFFFF)); setColor(video::SColor(0xFFFFFFFF));
return; return;
@ -394,8 +401,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
wscale, tsrc, wscale, tsrc,
l0.animation_frame_count); l0.animation_frame_count);
// Add color // Add color
m_colors.emplace_back(l0.has_color, l0.color); m_buffer_info.emplace_back(l0.has_color, l0.color);
m_colors.emplace_back(l1.has_color, l1.color); m_buffer_info.emplace_back(l1.has_color, l1.color);
break; break;
} }
case NDT_PLANTLIKE_ROOTED: { case NDT_PLANTLIKE_ROOTED: {
@ -404,7 +411,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
setExtruded(tsrc->getTextureName(l0.texture_id), setExtruded(tsrc->getTextureName(l0.texture_id),
"", wield_scale, tsrc, "", wield_scale, tsrc,
l0.animation_frame_count); l0.animation_frame_count);
m_colors.emplace_back(l0.has_color, l0.color); m_buffer_info.emplace_back(l0.has_color, l0.color);
break; break;
} }
default: { default: {
@ -413,7 +420,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
if (def.place_param2) if (def.place_param2)
n.setParam2(*def.place_param2); n.setParam2(*def.place_param2);
mesh = createGenericNodeMesh(client, n, &m_colors, f); mesh = createGenericNodeMesh(client, n, &m_buffer_info, f);
changeToMesh(mesh); changeToMesh(mesh);
mesh->drop(); mesh->drop();
m_meshnode->setScale( m_meshnode->setScale(
@ -447,9 +454,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
setExtruded("no_texture.png", "", def.wield_scale, tsrc, 1); setExtruded("no_texture.png", "", def.wield_scale, tsrc, 1);
} }
m_colors.emplace_back(); m_buffer_info.emplace_back();
// overlay is white, if present // overlay is white, if present
m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); m_buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF));
// initialize the color // initialize the color
setColor(video::SColor(0xFFFFFFFF)); setColor(video::SColor(0xFFFFFFFF));
@ -471,33 +478,38 @@ void WieldMeshSceneNode::setColor(video::SColor c)
u8 blue = c.getBlue(); u8 blue = c.getBlue();
const u32 mc = mesh->getMeshBufferCount(); const u32 mc = mesh->getMeshBufferCount();
if (mc > m_colors.size()) if (mc > m_buffer_info.size())
m_colors.resize(mc); m_buffer_info.resize(mc);
for (u32 j = 0; j < mc; j++) { for (u32 j = 0; j < mc; j++) {
video::SColor bc(m_base_color); video::SColor bc(m_base_color);
m_colors[j].applyOverride(bc); m_buffer_info[j].applyOverride(bc);
video::SColor buffercolor(255, video::SColor buffercolor(255,
bc.getRed() * red / 255, bc.getRed() * red / 255,
bc.getGreen() * green / 255, bc.getGreen() * green / 255,
bc.getBlue() * blue / 255); bc.getBlue() * blue / 255);
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
if (m_colors[j].needColorize(buffercolor)) { if (m_buffer_info[j].needColorize(buffercolor)) {
buf->setDirty(scene::EBT_VERTEX); buf->setDirty(scene::EBT_VERTEX);
setMeshBufferColor(buf, buffercolor); setMeshBufferColor(buf, buffercolor);
} }
} }
} }
void WieldMeshSceneNode::setNodeLightColor(video::SColor color) void WieldMeshSceneNode::setLightColorAndAnimation(video::SColor color, float animation_time)
{ {
if (!m_meshnode) if (!m_meshnode)
return; return;
{
for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) { for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
// Color
video::SMaterial &material = m_meshnode->getMaterial(i); video::SMaterial &material = m_meshnode->getMaterial(i);
material.ColorParam = color; material.ColorParam = color;
// Animation
const ItemMeshBufferInfo &buf_info = m_buffer_info[i];
if (buf_info.animation_info) {
buf_info.animation_info->updateTexture(material, animation_time);
} }
} }
} }
@ -544,9 +556,9 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
const std::string inventory_overlay = item.getInventoryOverlay(idef); const std::string inventory_overlay = item.getInventoryOverlay(idef);
if (!inventory_image.empty()) { if (!inventory_image.empty()) {
mesh = getExtrudedMesh(tsrc, inventory_image, inventory_overlay); mesh = getExtrudedMesh(tsrc, inventory_image, inventory_overlay);
result->buffer_colors.emplace_back(); result->buffer_info.emplace_back();
// overlay is white, if present // overlay is white, if present
result->buffer_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); result->buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF));
// Items with inventory images do not need shading // Items with inventory images do not need shading
result->needs_shading = false; result->needs_shading = false;
} else if (def.type == ITEM_NODE && f.drawtype == NDT_AIRLIKE) { } else if (def.type == ITEM_NODE && f.drawtype == NDT_AIRLIKE) {
@ -562,8 +574,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
tsrc->getTextureName(l0.texture_id), tsrc->getTextureName(l0.texture_id),
tsrc->getTextureName(l1.texture_id)); tsrc->getTextureName(l1.texture_id));
// Add color // Add color
result->buffer_colors.emplace_back(l0.has_color, l0.color); result->buffer_info.emplace_back(l0.has_color, l0.color);
result->buffer_colors.emplace_back(l1.has_color, l1.color); result->buffer_info.emplace_back(l1.has_color, l1.color);
break; break;
} }
case NDT_PLANTLIKE_ROOTED: { case NDT_PLANTLIKE_ROOTED: {
@ -571,7 +583,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
const TileLayer &l0 = f.special_tiles[0].layers[0]; const TileLayer &l0 = f.special_tiles[0].layers[0];
mesh = getExtrudedMesh(tsrc, mesh = getExtrudedMesh(tsrc,
tsrc->getTextureName(l0.texture_id), ""); tsrc->getTextureName(l0.texture_id), "");
result->buffer_colors.emplace_back(l0.has_color, l0.color); result->buffer_info.emplace_back(l0.has_color, l0.color);
break; break;
} }
default: { default: {
@ -580,7 +592,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
if (def.place_param2) if (def.place_param2)
n.setParam2(*def.place_param2); n.setParam2(*def.place_param2);
mesh = createGenericNodeMesh(client, n, &result->buffer_colors, f); mesh = createGenericNodeMesh(client, n, &result->buffer_info, f);
scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
break; break;
} }

View file

@ -11,6 +11,8 @@
#include <EMaterialTypes.h> #include <EMaterialTypes.h>
#include <IMeshSceneNode.h> #include <IMeshSceneNode.h>
#include <SColor.h> #include <SColor.h>
#include <memory>
#include "tile.h"
namespace irr::scene namespace irr::scene
{ {
@ -28,9 +30,10 @@ struct ContentFeatures;
class ShadowRenderer; class ShadowRenderer;
/* /*
* Holds color information of an item mesh's buffer. * Holds information of an item mesh's buffer.
* Used for coloring and animation.
*/ */
class ItemPartColor class ItemMeshBufferInfo
{ {
/* /*
* Optional color that overrides the global base color. * Optional color that overrides the global base color.
@ -47,12 +50,14 @@ class ItemPartColor
public: public:
ItemPartColor() = default; ItemMeshBufferInfo() = default;
ItemPartColor(bool override, video::SColor color) : ItemMeshBufferInfo(bool override, video::SColor color) :
override_color(color), override_color_set(override) override_color(color), override_color_set(override)
{} {}
ItemMeshBufferInfo(const TileLayer &layer);
void applyOverride(video::SColor &dest) const { void applyOverride(video::SColor &dest) const {
if (override_color_set) if (override_color_set)
dest = override_color; dest = override_color;
@ -65,15 +70,18 @@ public:
last_colorized = target; last_colorized = target;
return true; return true;
} }
// Null for no animated parts
std::unique_ptr<AnimationInfo> animation_info;
}; };
struct ItemMesh struct ItemMesh
{ {
scene::IMesh *mesh = nullptr; scene::IMesh *mesh = nullptr;
/* /*
* Stores the color of each mesh buffer. * Stores draw information of each mesh buffer.
*/ */
std::vector<ItemPartColor> buffer_colors; std::vector<ItemMeshBufferInfo> buffer_info;
/* /*
* If false, all faces of the item should have the same brightness. * If false, all faces of the item should have the same brightness.
* Disables shading based on normal vectors. * Disables shading based on normal vectors.
@ -101,7 +109,7 @@ public:
// Must only be used if the constructor was called with lighting = false // Must only be used if the constructor was called with lighting = false
void setColor(video::SColor color); void setColor(video::SColor color);
void setNodeLightColor(video::SColor color); void setLightColorAndAnimation(video::SColor color, float animation_time);
scene::IMesh *getMesh() { return m_meshnode->getMesh(); } scene::IMesh *getMesh() { return m_meshnode->getMesh(); }
@ -120,10 +128,10 @@ private:
bool m_bilinear_filter; bool m_bilinear_filter;
bool m_trilinear_filter; bool m_trilinear_filter;
/*! /*!
* Stores the colors of the mesh's mesh buffers. * Stores the colors and animation data of the mesh's mesh buffers.
* This does not include lighting. * This does not include lighting.
*/ */
std::vector<ItemPartColor> m_colors; std::vector<ItemMeshBufferInfo> m_buffer_info;
/*! /*!
* The base color of this mesh. This is the default * The base color of this mesh. This is the default
* for all mesh buffers. * for all mesh buffers.

View file

@ -118,13 +118,13 @@ void drawItemStack(
client->idef()->getItemstackColor(item, client); client->idef()->getItemstackColor(item, client);
const u32 mc = mesh->getMeshBufferCount(); const u32 mc = mesh->getMeshBufferCount();
if (mc > imesh->buffer_colors.size()) if (mc > imesh->buffer_info.size())
imesh->buffer_colors.resize(mc); imesh->buffer_info.resize(mc);
for (u32 j = 0; j < mc; ++j) { for (u32 j = 0; j < mc; ++j) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
video::SColor c = basecolor; video::SColor c = basecolor;
auto &p = imesh->buffer_colors[j]; auto &p = imesh->buffer_info[j];
p.applyOverride(c); p.applyOverride(c);
// TODO: could be moved to a shader // TODO: could be moved to a shader
@ -137,6 +137,12 @@ void drawItemStack(
} }
video::SMaterial &material = buf->getMaterial(); video::SMaterial &material = buf->getMaterial();
// Texture animation
if (p.animation_info) {
p.animation_info->updateTexture(material, client->getAnimationTime());
}
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
driver->setMaterial(material); driver->setMaterial(material);
driver->drawMeshBuffer(buf); driver->drawMeshBuffer(buf);