From 98295cf839c41957aa98ba35bfac5a3cf4da6d23 Mon Sep 17 00:00:00 2001 From: cx384 Date: Sat, 8 Nov 2025 21:49:01 +0100 Subject: [PATCH] Move client code from nodedef.h/cpp (#16615) --- src/client/CMakeLists.txt | 1 + src/client/client.cpp | 4 +- src/client/clientmap.cpp | 5 +- src/client/content_mapblock.cpp | 22 +- src/client/content_mapblock.h | 1 + src/client/hud.cpp | 2 +- src/client/item_visuals_manager.cpp | 1 + src/client/mapblock_mesh.cpp | 14 +- src/client/mesh.cpp | 2 +- src/client/mesh.h | 3 +- src/client/minimap.cpp | 10 +- src/client/node_visuals.cpp | 656 +++++++++++++++++++++++++ src/client/node_visuals.h | 87 ++++ src/client/particles.cpp | 6 +- src/client/shader.h | 1 + src/client/shadows/shadowsScreenQuad.h | 2 + src/client/wieldmesh.cpp | 16 +- src/dummygamedef.h | 7 + src/gui/drawItemStack.cpp | 2 + src/mapgen/mapgen.h | 1 + src/mapgen/mg_biome.h | 1 + src/mapnode.cpp | 9 - src/mapnode.h | 8 - src/nodedef.cpp | 656 +------------------------ src/nodedef.h | 94 +--- src/script/common/c_content.cpp | 18 +- src/unittest/test_content_mapblock.cpp | 40 +- 27 files changed, 880 insertions(+), 789 deletions(-) create mode 100644 src/client/node_visuals.cpp create mode 100644 src/client/node_visuals.h diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index ac98d4bd88..eaf195c486 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -66,6 +66,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/mesh.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mesh_generator_thread.cpp ${CMAKE_CURRENT_SOURCE_DIR}/minimap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/node_visuals.cpp ${CMAKE_CURRENT_SOURCE_DIR}/particles.cpp ${CMAKE_CURRENT_SOURCE_DIR}/renderingengine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shader.cpp diff --git a/src/client/client.cpp b/src/client/client.cpp index 01f9758ed5..b0a0f0db19 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -30,6 +30,7 @@ #include "mapnode.h" #include "mapsector.h" #include "minimap.h" +#include "node_visuals.h" #include "profiler.h" #include "shader.h" #include "translation.h" @@ -57,6 +58,7 @@ #include "database/database-files.h" #include "database/database-sqlite3.h" +#include #include #include @@ -1880,7 +1882,7 @@ void Client::afterContentReceived() TextureUpdateArgs tu_args; tu_args.last_time_ms = porting::getTimeMs(); tu_args.text_base = wstrgettext("Initializing nodes"); - m_nodedef->updateTextures(this, &tu_args); + NodeVisuals::fillNodeVisuals(m_nodedef, this, &tu_args); // Start mesh update thread after setting up content definitions infostream<<"- Starting mesh update thread"< #include "mapsector.h" #include "mapblock.h" +#include "node_visuals.h" #include "nodedef.h" #include "player.h" // CameraMode #include "profiler.h" @@ -401,7 +402,7 @@ void ClientMap::updateDrawList() bool occlusion_culling_enabled = mesh_grid.cell_size < 4; if (m_control.allow_noclip) { MapNode n = getNode(cam_pos_nodes); - if (n.getContent() == CONTENT_IGNORE || m_nodedef->get(n).solidness == 2) + if (n.getContent() == CONTENT_IGNORE || m_nodedef->get(n).visuals->solidness == 2) occlusion_culling_enabled = false; } @@ -1393,7 +1394,7 @@ void ClientMap::renderPostFx(CameraMode cam_mode) // If the camera is in a solid node, make everything black. // (first person mode only) - if (features.solidness == 2 && cam_mode == CAMERA_MODE_FIRST && + if (features.visuals->solidness == 2 && cam_mode == CAMERA_MODE_FIRST && !m_control.allow_noclip) { post_color = video::SColor(255, 0, 0, 0); } diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index cf02e5e129..2f75dce9a5 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -9,7 +9,7 @@ #include "util/directiontables.h" #include "util/tracy_wrapper.h" #include "mapblock_mesh.h" -#include "settings.h" +#include "node_visuals.h" #include "nodedef.h" #include "client/tile.h" #include "mesh.h" @@ -98,7 +98,8 @@ void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile_ret) // Returns a special tile, ready for use, non-rotated. void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile_ret, bool apply_crack) { - *tile_ret = cur_node.f->special_tiles[index]; + const ContentFeatures &f = *cur_node.f; + *tile_ret = f.visuals->special_tiles[index]; TileLayer *top_layer = nullptr; for (auto &layernum : tile_ret->layers) { @@ -107,7 +108,7 @@ void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile_ret, bool a continue; top_layer = layer; if (!layer->has_color) - cur_node.n.getColor(*cur_node.f, &layer->color); + f.visuals->getColor(cur_node.n.param2, &layer->color); } if (apply_crack) @@ -451,12 +452,12 @@ void MapblockMeshGenerator::drawSolidNode() continue; if (n2 != CONTENT_AIR) { const ContentFeatures &f2 = nodedef->get(n2); - if (f2.solidness == 2) + if (f2.visuals->solidness == 2) continue; if (cur_node.f->drawtype == NDT_LIQUID) { if (cur_node.f->sameLiquidRender(f2)) continue; - backface_culling = f2.solidness || f2.visual_solidness; + backface_culling = f2.visuals->solidness || f2.visuals->visual_solidness; } } faces |= 1 << face; @@ -565,7 +566,7 @@ void MapblockMeshGenerator::prepareLiquidNodeDrawing() && (nbottom.getContent() != cur_liquid.c_source); if (cur_liquid.draw_bottom) { const ContentFeatures &f2 = nodedef->get(nbottom.getContent()); - if (f2.solidness > 1) + if (f2.visuals->solidness > 1) cur_liquid.draw_bottom = false; } @@ -707,7 +708,7 @@ void MapblockMeshGenerator::drawLiquidSides() const ContentFeatures &neighbor_features = nodedef->get(neighbor.content); // Don't draw face if neighbor is blocking the view - if (neighbor_features.solidness == 2) + if (neighbor_features.visuals->solidness == 2) continue; video::S3DVertex vertices[4]; @@ -1023,7 +1024,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() // Liquid is textured with 1 tile defined in nodedef 'special_tiles' auto &cf = *cur_node.f; if (param2 > 0 && cf.param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL && - !cf.special_tiles[0].layers[0].empty()) { + !cf.visuals->special_tiles[0].layers[0].empty()) { // Internal liquid level has param2 range 0 .. 63, // convert it to -0.5 .. 0.5 float vlev = (param2 / 63.0f) * 2.0f - 1.0f; @@ -1709,9 +1710,10 @@ void MapblockMeshGenerator::drawMeshNode() degrotate = cur_node.n.getDegRotate(nodedef); } - if (cur_node.f->mesh_ptr) { + auto *mesh_ptr = cur_node.f->visuals->mesh_ptr; + if (mesh_ptr) { // clone and rotate mesh - mesh = cloneStaticMesh(cur_node.f->mesh_ptr); + mesh = cloneStaticMesh(mesh_ptr); bool modified = true; if (facedir) rotateMeshBy6dFacedir(mesh, facedir); diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index d1dd6f20b4..359ad39b36 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -5,6 +5,7 @@ #pragma once #include "nodedef.h" +#include "tile.h" struct MeshMakeData; struct MeshCollector; diff --git a/src/client/hud.cpp b/src/client/hud.cpp index c149dd7ef3..1d36087f76 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -14,7 +14,6 @@ #include "client.h" #include "inventory.h" #include "shader.h" -#include "client/tile.h" #include "localplayer.h" #include "camera.h" #include "fontengine.h" @@ -28,6 +27,7 @@ #include "irrlicht_changes/CGUITTFont.h" #include "gui/drawItemStack.h" #include +#include #define OBJECT_CROSSHAIR_LINE_SIZE 8 #define CROSSHAIR_LINE_SIZE 10 diff --git a/src/client/item_visuals_manager.cpp b/src/client/item_visuals_manager.cpp index 6013c7121f..6007910c73 100644 --- a/src/client/item_visuals_manager.cpp +++ b/src/client/item_visuals_manager.cpp @@ -9,6 +9,7 @@ #include "texturesource.h" #include "itemdef.h" #include "inventory.h" +#include struct ItemVisualsManager::ItemVisuals { diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 7a9dc994c7..b845f6f4b1 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -6,19 +6,15 @@ #include "CMeshBuffer.h" #include "client.h" #include "mapblock.h" -#include "map.h" -#include "noise.h" -#include "profiler.h" +#include "node_visuals.h" #include "shader.h" #include "mesh.h" #include "minimap.h" #include "content_mapblock.h" -#include "util/directiontables.h" #include "util/tracy_wrapper.h" #include "client/meshgen/collector.h" #include "client/renderingengine.h" #include -#include #include #include "client/texturesource.h" #include @@ -166,7 +162,7 @@ static u16 getSmoothLightCombined(const v3s16 &p, if (f.light_source > light_source_max) light_source_max = f.light_source; // Check f.solidness because fast-style leaves look better this way - if (f.param_type == CPT_LIGHT && f.solidness != 2) { + if (f.param_type == CPT_LIGHT && f.visuals->solidness != 2) { u8 light_level_day = n.getLight(LIGHTBANK_DAY, f.getLightingFlags()); u8 light_level_night = n.getLight(LIGHTBANK_NIGHT, f.getLightingFlags()); if (light_level_day == LIGHT_SUN) @@ -335,13 +331,13 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, { const NodeDefManager *ndef = data->m_nodedef; const ContentFeatures &f = ndef->get(mn); - tile = f.tiles[tileindex]; + tile = f.visuals->tiles[tileindex]; bool has_crack = p == data->m_crack_pos_relative; for (TileLayer &layer : tile.layers) { if (layer.empty()) continue; if (!layer.has_color) - mn.getColor(f, &(layer.color)); + f.visuals->getColor(mn.param2, &(layer.color)); // Apply temporary crack if (has_crack) layer.material_flags |= MATERIAL_FLAG_CRACK; @@ -914,7 +910,7 @@ u8 get_solid_sides(MeshMakeData *data) for (u8 k = 0; k < 6; k++) { const MapNode &top = data->m_vmanip.getNodeRefUnsafe(blockpos_nodes + positions[k]); - if (ndef->get(top).solidness != 2) + if (ndef->get(top).visuals->solidness != 2) result &= ~(1 << k); } } diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index e4da3ae045..61a919c94a 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -5,8 +5,8 @@ #include "mesh.h" #include "IMeshBuffer.h" #include "SSkinMeshBuffer.h" +#include "constants.h" #include "debug.h" -#include "log.h" #include #include #include "S3DVertex.h" diff --git a/src/client/mesh.h b/src/client/mesh.h index df6376b105..218c4a5ad1 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -4,14 +4,15 @@ #pragma once +#include "irrlichttypes_bloated.h" #include "SColor.h" #include "SMaterialLayer.h" -#include "nodedef.h" namespace scene { class IAnimatedMesh; class IMesh; class IMeshBuffer; + struct SMesh; } diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 9f32bbe393..ead088bba1 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -7,6 +7,7 @@ #include "camera.h" #include "client.h" #include "mapblock.h" // getNodeBlockPos +#include "node_visuals.h" #include "settings.h" #include "shader.h" #include "client/renderingengine.h" @@ -414,12 +415,13 @@ void Minimap::blitMinimapPixelsToImageSurface( } else if (overlay.name.empty() && tile.has_color) { tilecolor = tile.color; } else { - mmpixel->n.getColor(f, &tilecolor); + f.visuals->getColor(mmpixel->n.param2, &tilecolor); } // Multiply with pre-generated "color of texture" - tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255); - tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255); - tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255); + video::SColor &minimap_color = f.visuals->minimap_color; + tilecolor.setRed(tilecolor.getRed() * minimap_color.getRed() / 255); + tilecolor.setGreen(tilecolor.getGreen() * minimap_color.getGreen() / 255); + tilecolor.setBlue(tilecolor.getBlue() * minimap_color.getBlue() / 255); tilecolor.setAlpha(240); map_image->setPixel(x, data->mode.map_size - z - 1, tilecolor); diff --git a/src/client/node_visuals.cpp b/src/client/node_visuals.cpp new file mode 100644 index 0000000000..9ca615b3d3 --- /dev/null +++ b/src/client/node_visuals.cpp @@ -0,0 +1,656 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 cx384 + +#include "node_visuals.h" + +#include "mesh.h" +#include "shader.h" +#include "client.h" +#include "renderingengine.h" +#include "texturesource.h" +#include "wieldmesh.h" // createAnimationFrames +#include "tile.h" +#include +#include +#include + +struct TileAttribContext { + ITextureSource *tsrc; + PreLoadedTextures *texture_pool; + video::SColor base_color; + const TextureSettings &tsettings; +}; + +struct ShaderIds { + u32 normal; + // Shader that will handle an array texture and texture_layer_idx + u32 with_layers; +}; + +/* + Texture pool and related +*/ + +struct PreLoadedTexture { + video::ITexture *texture = nullptr; + u32 texture_id = 0; + u16 texture_layer_idx = 0; + bool used = false; // For debugging +}; + +struct PreLoadedTextures { + std::unordered_map pool; + std::unordered_set missed; // For debugging + + PreLoadedTexture find(const std::string &name); + void add(const std::string &name, const PreLoadedTexture &t); + + void printStats(std::ostream &to) const; +}; + +PreLoadedTexture PreLoadedTextures::find(const std::string &name) +{ + auto it = pool.find(name); + if (it == pool.end()) { + missed.emplace(name); + return {}; + } + it->second.used = true; + return it->second; +} + +void PreLoadedTextures::add(const std::string &name, const PreLoadedTexture &t) +{ + assert(pool.find(name) == pool.end()); + pool[name] = t; +} + +void PreLoadedTextures::printStats(std::ostream &to) const +{ + size_t unused = 0; + for (auto &it : pool) + unused += it.second.used ? 0 : 1; + to << "PreLoadedTextures: " << pool.size() << "\n wasted: " << unused + << " missed: " << missed.size() << std::endl; +} + + +static void fillTileAttribs(TileLayer *layer, TileAttribContext context, + const TileSpec &tile, const TileDef &tiledef, + MaterialType material_type, ShaderIds shader) +{ + auto *tsrc = context.tsrc; + const auto &tsettings = context.tsettings; + + std::string texture_image; + if (!tiledef.name.empty()) { + texture_image = tiledef.name; + if (tsrc->needFilterForMesh()) + texture_image += tsrc->FILTER_FOR_MESH; + } else { + // Tile is empty, nothing to do. + return; + } + + core::dimension2du texture_size; + if (!texture_image.empty()) + texture_size = tsrc->getTextureDimensions(texture_image); + if (!texture_size.Width || !texture_size.Height) + texture_size = {1, 1}; // dummy if there's an error + + // Scale + bool has_scale = tiledef.scale > 0; + bool use_autoscale = tsettings.autoscale_mode == AUTOSCALE_FORCE || + (tsettings.autoscale_mode == AUTOSCALE_ENABLE && !has_scale); + if (use_autoscale) { + float base_size = tsettings.node_texture_size; + float size = std::fmin(texture_size.Width, texture_size.Height); + layer->scale = std::fmax(base_size, size) / base_size; + } else if (has_scale) { + layer->scale = tiledef.scale; + } else { + layer->scale = 1; + } + if (!tile.world_aligned) + layer->scale = 1; + + // Material + layer->material_type = material_type; + layer->material_flags = 0; + if (tiledef.backface_culling) + layer->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; + if (tiledef.animation.type != TAT_NONE) + layer->material_flags |= MATERIAL_FLAG_ANIMATION; + if (tiledef.tileable_horizontal) + layer->material_flags |= MATERIAL_FLAG_TILEABLE_HORIZONTAL; + if (tiledef.tileable_vertical) + layer->material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL; + + // Color + layer->has_color = tiledef.has_color; + if (tiledef.has_color) + layer->color = tiledef.color; + else + layer->color = context.base_color; + + // Animation + if (layer->material_flags & MATERIAL_FLAG_ANIMATION) { + int frame_length_ms = 0; + std::vector frames = createAnimationFrames( + tsrc, tiledef.name, tiledef.animation, frame_length_ms); + if (frames.size() > 1) { + layer->frames = new std::vector(frames); + layer->animation_frame_count = layer->frames->size(); + layer->animation_frame_length_ms = frame_length_ms; + + // Set default texture to first frame (not used practice) + layer->texture = (*layer->frames)[0].texture; + layer->texture_id = (*layer->frames)[0].texture_id; + } else { + layer->material_flags &= ~MATERIAL_FLAG_ANIMATION; + } + } + + if (!(layer->material_flags & MATERIAL_FLAG_ANIMATION)) { + // Grab texture + auto tex = context.texture_pool->find(texture_image); + if (!tex.texture) { + // wasn't pre-loaded: create standard texture on the fly + layer->texture = tsrc->getTexture(texture_image, &layer->texture_id); + } else { + layer->texture = tex.texture; + layer->texture_id = tex.texture_id; + layer->texture_layer_idx = tex.texture_layer_idx; + } + } + + // Decide on shader to use + if (layer->texture) { + layer->shader_id = (layer->texture->getType() == video::ETT_2D_ARRAY) ? + shader.with_layers : shader.normal; + } +} + +static bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType drawtype) +{ + if (style == ALIGN_STYLE_WORLD) + return true; + if (mode == WORLDALIGN_DISABLE) + return false; + if (style == ALIGN_STYLE_USER_DEFINED) + return true; + if (drawtype == NDT_NORMAL) + return mode >= WORLDALIGN_FORCE; + if (drawtype == NDT_NODEBOX) + return mode >= WORLDALIGN_FORCE_NODEBOX; + return false; +} + +/// @return maximum number of layers in array textures we can use (0 if unsupported) +static size_t getArrayTextureMax(IShaderSource *shdsrc) +{ + auto *driver = RenderingEngine::get_video_driver(); + // needs to support creating array textures + if (!driver->queryFeature(video::EVDF_TEXTURE_2D_ARRAY)) + return 0; + // must support sampling from them + if (!shdsrc->supportsSampler2DArray()) + return 0; + // shadow shaders can't handle array textures yet (TODO) + if (g_settings->getBool("enable_dynamic_shadows")) + return 0; + + u32 n = driver->getLimits().MaxArrayTextureImages; + constexpr u32 type_max = std::numeric_limits::max(); + n = std::min(n, type_max); + n = std::min(n, g_settings->getU32("array_texture_max")); + return n; +} + + +//// NodeVisuals + +NodeVisuals::~NodeVisuals() +{ + for (u16 j = 0; j < 6; j++) { + delete tiles[j].layers[0].frames; + delete tiles[j].layers[1].frames; + } + for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) { + delete special_tiles[j].layers[0].frames; + delete special_tiles[j].layers[1].frames; + } + if (mesh_ptr) + mesh_ptr->drop(); +} + +void NodeVisuals::preUpdateTextures(ITextureSource *tsrc, + std::unordered_set &pool, const TextureSettings &tsettings) +{ + // Find out the exact texture strings this node might use, and put them into the pool + // (this should match updateTextures, but it's not the end of the world if + // a mismatch occurs) + std::string append; + if (tsrc->needFilterForMesh()) + append = ITextureSource::FILTER_FOR_MESH; + std::string append_overlay = append, append_special = append; + bool use = true, use_overlay = true, use_special = true; + + if (f->drawtype == NDT_ALLFACES_OPTIONAL) { + use_special = (tsettings.leaves_style == LEAVES_SIMPLE); + use = !use_special; + if (tsettings.leaves_style == LEAVES_OPAQUE) + append.insert(0, "^[noalpha"); + } + + const auto &consider_tile = [&] (const TileDef &def, const std::string &append) { + // Animations are chopped into frames later, so we won't actually need + // the source texture + if (!def.name.empty() && def.animation.type == TAT_NONE) { + pool.insert(def.name + append); + } + }; + + for (u32 j = 0; j < 6; j++) { + if (use) + consider_tile(f->tiledef[j], append); + } + for (u32 j = 0; j < 6; j++) { + if (use_overlay) + consider_tile(f->tiledef_overlay[j], append_overlay); + } + for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) { + if (use_special) + consider_tile(f->tiledef_special[j], append_special); + } +} + +void NodeVisuals::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, Client *client, + PreLoadedTextures *texture_pool, const TextureSettings &tsettings) +{ + // Things needed form ContentFeatures + auto &alpha = f->alpha; + auto &drawtype = f->drawtype; + const auto &tiledef = f->tiledef; + const auto &tiledef_overlay = f->tiledef_overlay; + const auto &tiledef_special = f->tiledef_special; + const auto &waving = f->waving; + const auto &color = f->color; + const auto ¶m_type_2 = f->param_type_2; + const auto &palette_name = f->palette_name; + + // Figure out the actual tiles to use + TileDef tdef[6]; + for (u32 j = 0; j < 6; j++) { + tdef[j] = tiledef[j]; + if (tdef[j].name.empty()) { + tdef[j].name = "no_texture.png"; + tdef[j].backface_culling = false; + } + } + // also the overlay tiles + TileDef tdef_overlay[6]; + for (u32 j = 0; j < 6; j++) + tdef_overlay[j] = tiledef_overlay[j]; + // also the special tiles + TileDef tdef_spec[6]; + for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) { + tdef_spec[j] = tiledef_special[j]; + } + + bool is_liquid = false; + + MaterialType material_type = alpha_mode_to_material_type(alpha); + + switch (drawtype) { + default: + case NDT_NORMAL: + solidness = 2; + break; + case NDT_AIRLIKE: + solidness = 0; + break; + case NDT_LIQUID: + if (!tsettings.translucent_liquids) + alpha = ALPHAMODE_OPAQUE; + solidness = 1; + is_liquid = true; + break; + case NDT_FLOWINGLIQUID: + solidness = 0; + if (!tsettings.translucent_liquids) + alpha = ALPHAMODE_OPAQUE; + is_liquid = true; + break; + case NDT_GLASSLIKE: + solidness = 0; + visual_solidness = 1; + break; + case NDT_GLASSLIKE_FRAMED: + solidness = 0; + visual_solidness = 1; + break; + case NDT_GLASSLIKE_FRAMED_OPTIONAL: + solidness = 0; + visual_solidness = 1; + drawtype = tsettings.connected_glass ? NDT_GLASSLIKE_FRAMED : NDT_GLASSLIKE; + break; + case NDT_ALLFACES: + solidness = 0; + visual_solidness = 1; + break; + case NDT_ALLFACES_OPTIONAL: + if (tsettings.leaves_style == LEAVES_FANCY) { + drawtype = NDT_ALLFACES; + solidness = 0; + visual_solidness = 1; + } else if (tsettings.leaves_style == LEAVES_SIMPLE) { + for (u32 j = 0; j < 6; j++) { + if (!tdef_spec[j].name.empty()) + tdef[j].name = tdef_spec[j].name; + } + drawtype = NDT_GLASSLIKE; + solidness = 0; + visual_solidness = 1; + } else { + if (waving >= 1) { + // waving nodes must make faces so there are no gaps + drawtype = NDT_ALLFACES; + solidness = 0; + visual_solidness = 1; + } else { + drawtype = NDT_NORMAL; + solidness = 2; + } + for (TileDef &td : tdef) + td.name += std::string("^[noalpha"); + } + if (waving >= 1) + material_type = TILE_MATERIAL_WAVING_LEAVES; + break; + case NDT_PLANTLIKE: + solidness = 0; + if (waving >= 1) + material_type = TILE_MATERIAL_WAVING_PLANTS; + break; + case NDT_FIRELIKE: + solidness = 0; + break; + case NDT_MESH: + case NDT_NODEBOX: + solidness = 0; + if (waving == 1) { + material_type = TILE_MATERIAL_WAVING_PLANTS; + } else if (waving == 2) { + material_type = TILE_MATERIAL_WAVING_LEAVES; + } else if (waving == 3) { + material_type = alpha == ALPHAMODE_OPAQUE ? + TILE_MATERIAL_WAVING_LIQUID_OPAQUE : (alpha == ALPHAMODE_CLIP ? + TILE_MATERIAL_WAVING_LIQUID_BASIC : TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT); + } + break; + case NDT_TORCHLIKE: + case NDT_SIGNLIKE: + case NDT_FENCELIKE: + case NDT_RAILLIKE: + solidness = 0; + break; + case NDT_PLANTLIKE_ROOTED: + solidness = 2; + break; + } + + if (is_liquid) { + if (waving == 3) { + material_type = alpha == ALPHAMODE_OPAQUE ? + TILE_MATERIAL_WAVING_LIQUID_OPAQUE : (alpha == ALPHAMODE_CLIP ? + TILE_MATERIAL_WAVING_LIQUID_BASIC : TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT); + } else { + material_type = alpha == ALPHAMODE_OPAQUE ? TILE_MATERIAL_LIQUID_OPAQUE : + TILE_MATERIAL_LIQUID_TRANSPARENT; + } + } + + const bool texture_2d_array = getArrayTextureMax(shdsrc) > 1; + const auto &getNodeShader = [&] (MaterialType my_material, NodeDrawType my_drawtype) { + ShaderIds ret; + ret.normal = shdsrc->getShader("nodes_shader", my_material, my_drawtype); + // need to avoid generating the shader if unsupported + if (texture_2d_array) + ret.with_layers = shdsrc->getShader("nodes_shader", my_material, my_drawtype, true); + return ret; + }; + + ShaderIds tile_shader = getNodeShader(material_type, drawtype); + + MaterialType overlay_material = material_type_with_alpha(material_type); + + ShaderIds overlay_shader = getNodeShader(overlay_material, drawtype); + + // minimap pixel color = average color of top tile + if (tsettings.enable_minimap && drawtype != NDT_AIRLIKE && !tdef[0].name.empty()) + { + if (!tdef_overlay[0].name.empty()) { + // Merge overlay and base texture + std::string combined = tdef[0].name + "^(" + tdef_overlay[0].name + ")"; + minimap_color = tsrc->getTextureAverageColor(combined); + } else { + minimap_color = tsrc->getTextureAverageColor(tdef[0].name); + } + } + + // Tiles (fill in f->tiles[]) + bool any_polygon_offset = false; + TileAttribContext tac{tsrc, texture_pool, color, tsettings}; + + for (u16 j = 0; j < 6; j++) { + tiles[j].world_aligned = isWorldAligned(tdef[j].align_style, + tsettings.world_aligned_mode, drawtype); + fillTileAttribs(&tiles[j].layers[0], tac, tiles[j], tdef[j], + material_type, tile_shader); + if (!tdef_overlay[j].name.empty()) { + tdef_overlay[j].backface_culling = tdef[j].backface_culling; + fillTileAttribs(&tiles[j].layers[1], tac, tiles[j], tdef_overlay[j], + overlay_material, overlay_shader); + } + + tiles[j].layers[0].need_polygon_offset = !tiles[j].layers[1].empty(); + any_polygon_offset |= tiles[j].layers[0].need_polygon_offset; + } + + if (drawtype == NDT_MESH && any_polygon_offset) { + // Our per-tile polygon offset enablement workaround works fine for normal + // nodes and anything else, where we know that different tiles are different + // faces that couldn't possibly conflict with each other. + // We can't assume this for mesh nodes, so apply it to all tiles (= materials) + // then. + for (u16 j = 0; j < 6; j++) + tiles[j].layers[0].need_polygon_offset = true; + } + + MaterialType special_material = material_type; + if (drawtype == NDT_PLANTLIKE_ROOTED) { + if (waving == 1) + special_material = TILE_MATERIAL_WAVING_PLANTS; + else if (waving == 2) + special_material = TILE_MATERIAL_WAVING_LEAVES; + } + + ShaderIds special_shader = getNodeShader(special_material, drawtype); + + // Special tiles (fill in f->special_tiles[]) + for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) { + fillTileAttribs(&special_tiles[j].layers[0], tac, + special_tiles[j], tdef_spec[j], special_material, special_shader); + } + + if (param_type_2 == CPT2_COLOR || + param_type_2 == CPT2_COLORED_FACEDIR || + param_type_2 == CPT2_COLORED_4DIR || + param_type_2 == CPT2_COLORED_WALLMOUNTED || + param_type_2 == CPT2_COLORED_DEGROTATE) + palette = tsrc->getPalette(palette_name); +} + +void NodeVisuals::updateMesh(Client *client, const TextureSettings &tsettings) +{ + auto *manip = client->getSceneManager()->getMeshManipulator(); + (void)tsettings; + + const auto &mesh = f->mesh; + if (f->drawtype != NDT_MESH || mesh.empty()) + return; + + // Note: By freshly reading, we get an unencumbered mesh. + if (scene::IMesh *src_mesh = client->getMesh(mesh)) { + bool apply_bs = false; + 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) * f->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; + manip->recalculateNormals(mesh_ptr, true, false); + } + } else { + mesh_ptr = nullptr; + } +} + +void NodeVisuals::collectMaterials(std::vector &leaves_materials) +{ + if (f->drawtype == NDT_AIRLIKE) + return; + + for (u16 j = 0; j < 6; j++) { + auto &l = tiles[j].layers; + if (!l[0].empty() && l[0].material_type == TILE_MATERIAL_WAVING_LEAVES) + leaves_materials.push_back(l[0].shader_id); + if (!l[1].empty() && l[1].material_type == TILE_MATERIAL_WAVING_LEAVES) + leaves_materials.push_back(l[1].shader_id); + } +} + +void NodeVisuals::getColor(u8 param2, video::SColor *color) const +{ + if (palette) { + *color = (*palette)[param2]; + return; + } + *color = f->color; +} + +void NodeVisuals::fillNodeVisuals(NodeDefManager *ndef, Client *client, void *progress_callback_args) +{ + infostream << "fillNodeVisuals: Updating " + "textures in node definitions" << std::endl; + ITextureSource *tsrc = client->tsrc(); + IShaderSource *shdsrc = client->getShaderSource(); + TextureSettings tsettings; + tsettings.readSettings(); + + tsrc->setImageCaching(true); + const u32 size = ndef->size(); + + /* collect all textures we might use */ + std::unordered_set pool; + ndef->applyFunction([&](ContentFeatures &f) { + assert(!f.visuals); + f.visuals = new NodeVisuals(&f); + f.visuals->preUpdateTextures(tsrc, pool, tsettings); + }); + + /* texture pre-loading stage */ + const size_t arraymax = getArrayTextureMax(shdsrc); + // Group by size + std::unordered_map> sizes; + if (arraymax > 1) { + infostream << "Using array textures with " << arraymax << " layers" << std::endl; + size_t i = 0; + for (auto &image : pool) { + core::dimension2du dim = tsrc->getTextureDimensions(image); + client->showUpdateProgressTexture(progress_callback_args, + 0.33333f * ++i / pool.size()); + if (!dim.Width || !dim.Height) // error + continue; + sizes[v2u32(dim)].emplace_back(image); + } + } + + // create array textures as far as possible + size_t num_preloadable = 0, preload_progress = 0; + for (auto &it : sizes) { + if (it.second.size() < 2) + continue; + num_preloadable += it.second.size(); + } + PreLoadedTextures plt; + const auto &doBunch = [&] (const std::vector &bunch) { + PreLoadedTexture t; + t.texture = tsrc->addArrayTexture(bunch, &t.texture_id); + preload_progress += bunch.size(); + client->showUpdateProgressTexture(progress_callback_args, + 0.33333f + 0.33333f * preload_progress / num_preloadable); + if (t.texture) { + // Success: all of the images in this bunch can now refer to this texture + for (size_t idx = 0; idx < bunch.size(); idx++) { + t.texture_layer_idx = idx; + plt.add(bunch[idx], t); + } + } + }; + for (auto &it : sizes) { + if (it.second.size() < 2) + continue; + std::vector bunch; + for (auto &image : it.second) { + bunch.emplace_back(image); + if (bunch.size() == arraymax) { + doBunch(bunch); + bunch.clear(); + } + } + if (!bunch.empty()) + doBunch(bunch); + } + // note that standard textures aren't preloaded + + /* final step */ + u32 progress = 0; + ndef->applyFunction([&](ContentFeatures &f) { + auto *v = f.visuals; + v->updateTextures(tsrc, shdsrc, client, &plt, tsettings); + v->updateMesh(client, tsettings); + v->collectMaterials(ndef->m_leaves_materials); + + client->showUpdateProgressTexture(progress_callback_args, + 0.66666f + 0.33333f * progress / size); + progress++; + }); + + SORT_AND_UNIQUE(ndef->m_leaves_materials); + verbosestream << "m_leaves_materials.size() = " << ndef->m_leaves_materials.size() + << std::endl; + + plt.printStats(infostream); + tsrc->setImageCaching(false); +} diff --git a/src/client/node_visuals.h b/src/client/node_visuals.h new file mode 100644 index 0000000000..c736f7aaa2 --- /dev/null +++ b/src/client/node_visuals.h @@ -0,0 +1,87 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 cx384 + +#pragma once + +#include +#include "nodedef.h" // CF_SPECIAL_COUNT +#include "tile.h" + +class Client; +struct PreLoadedTextures; +namespace scene +{ + class IMeshManipulator; + struct SMesh; +} + +// Stores client only data needed to draw nodes, like textures and meshes +// Contained in ContentFeatures + +struct NodeVisuals +{ + // 0 1 2 3 4 5 + // up down right left back front + TileSpec tiles[6]; + // Special tiles + TileSpec special_tiles[CF_SPECIAL_COUNT]; + u8 solidness = 2; // Used when choosing which face is drawn + u8 visual_solidness = 0; // When solidness=0, this tells how it looks like + bool backface_culling = true; + scene::SMesh *mesh_ptr = nullptr; // mesh in case of mesh node + video::SColor minimap_color; + std::vector *palette = nullptr; + + // alpha stays in ContentFeatures due to compatibility code that is necessary, + // because it was part of the node definition table in the past. + ~NodeVisuals(); + + // Get color from palette or content features + void getColor(u8 param2, video::SColor *color) const; + + /*! + * Creates NodeVisuals for every content feature in the passed NodeDefManager. + * @param ndef the NodeDefManager. + * @param client the Client. + * @param progress_cbk called each time a node is loaded. Arguments: + * `progress_cbk_args`, number of loaded ContentFeatures, number of + * total ContentFeatures. + * @param progress_cbk_args passed to the callback function + */ + static void fillNodeVisuals(NodeDefManager *ndef, Client *client, + void *progress_callback_args); + + DISABLE_CLASS_COPY(NodeVisuals); + +private: + NodeVisuals(ContentFeatures *features) : f{features} {} + friend class DummyGameDef; // Unittests need constructor + + ContentFeatures *f = nullptr; + + // Functions needed for initialisation + void preUpdateTextures(ITextureSource *tsrc, + std::unordered_set &pool, const TextureSettings &tsettings); + // May override the alpha and drawtype of the content features + void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, Client *client, + PreLoadedTextures *texture_pool, const TextureSettings &tsettings); + void updateMesh(Client *client, const TextureSettings &tsettings); + void collectMaterials(std::vector &leaves_materials); +}; + +/** + * @brief get fitting material type for an alpha mode + */ +static inline MaterialType alpha_mode_to_material_type(AlphaMode mode) +{ + switch (mode) { + case ALPHAMODE_BLEND: + return TILE_MATERIAL_ALPHA; + case ALPHAMODE_OPAQUE: + return TILE_MATERIAL_OPAQUE; + case ALPHAMODE_CLIP: + default: + return TILE_MATERIAL_BASIC; + } +} diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 35a7946522..81b62a3cde 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -14,9 +14,9 @@ #include "util/numeric.h" #include "light.h" #include "localplayer.h" -#include "environment.h" #include "clientmap.h" #include "mapnode.h" +#include "node_visuals.h" #include "nodedef.h" #include "client.h" #include "settings.h" @@ -891,7 +891,7 @@ bool ParticleManager::getNodeParticleParams(Client *client, const MapNode &n, else texid = myrand_range(0,5); - const TileLayer &tile = f.tiles[texid].layers[0]; + const TileLayer &tile = f.visuals->tiles[texid].layers[0]; *texture = extractTexture(f.tiledef[texid], tile, client->tsrc()); p.texture.blendmode = f.alpha == ALPHAMODE_BLEND ? BlendMode::alpha : BlendMode::clip; @@ -908,7 +908,7 @@ bool ParticleManager::getNodeParticleParams(Client *client, const MapNode &n, if (tile.has_color) *color = tile.color; else - n.getColor(f, color); + f.visuals->getColor(n.param2, color); return true; } diff --git a/src/client/shader.h b/src/client/shader.h index bdbf3b38fe..e5c6b23aec 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -12,6 +12,7 @@ #include #include #include "nodedef.h" +#include "tile.h" // MaterialType /* shader.{h,cpp}: Shader handling stuff. diff --git a/src/client/shadows/shadowsScreenQuad.h b/src/client/shadows/shadowsScreenQuad.h index e50e778fba..b63969dc44 100644 --- a/src/client/shadows/shadowsScreenQuad.h +++ b/src/client/shadows/shadowsScreenQuad.h @@ -4,6 +4,8 @@ #pragma once #include +#include +#include #include "client/shader.h" class ShadowScreenQuad diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index d4a84efdb5..f958a4a52e 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -8,6 +8,7 @@ #include "inventory.h" #include "client.h" #include "itemdef.h" +#include "node_visuals.h" #include "nodedef.h" #include "mesh.h" #include "content_mapblock.h" @@ -15,7 +16,6 @@ #include "client/meshgen/collector.h" #include "client/tile.h" #include "client/texturesource.h" -#include "log.h" #include "util/numeric.h" #include #include @@ -430,6 +430,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che const NodeDefManager *ndef = client->getNodeDefManager(); const ItemDefinition &def = item.getDefinition(idef); const ContentFeatures &f = ndef->get(def.name); + const NodeVisuals &v = *(f.visuals); { // Initialize material type used by setExtruded @@ -504,13 +505,13 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che v3f wscale = wield_scale; if (f.drawtype == NDT_FLOWINGLIQUID) wscale.Z *= 0.1f; - setExtruded(f.tiledef[0], f.tiles[0].layers[0], - f.tiledef_overlay[0], f.tiles[0].layers[1], wscale, tsrc); + setExtruded(f.tiledef[0], v.tiles[0].layers[0], + f.tiledef_overlay[0], v.tiles[0].layers[1], wscale, tsrc); break; } case NDT_PLANTLIKE_ROOTED: { // use the plant tile - setExtruded(f.tiledef_special[0], f.special_tiles[0].layers[0], + setExtruded(f.tiledef_special[0], v.special_tiles[0].layers[0], TileDef(), TileLayer(), wield_scale, tsrc); break; } @@ -646,6 +647,7 @@ void createItemMesh(Client *client, const ItemDefinition &def, IShaderSource *shdsrc = client->getShaderSource(); const NodeDefManager *ndef = client->getNodeDefManager(); const ContentFeatures &f = ndef->get(def.name); + const NodeVisuals &v = *(f.visuals); assert(result); FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized"); @@ -678,8 +680,8 @@ void createItemMesh(Client *client, const ItemDefinition &def, } else if (def.type == ITEM_NODE) { switch (f.drawtype) { case NDT_PLANTLIKE: { - const TileLayer &l0 = f.tiles[0].layers[0]; - const TileLayer &l1 = f.tiles[0].layers[1]; + const TileLayer &l0 = v.tiles[0].layers[0]; + const TileLayer &l1 = v.tiles[0].layers[1]; mesh = getExtrudedMesh( extractTexture(f.tiledef[0], l0, tsrc), extractTexture(f.tiledef[1], l1, tsrc)); @@ -690,7 +692,7 @@ void createItemMesh(Client *client, const ItemDefinition &def, } case NDT_PLANTLIKE_ROOTED: { // Use the plant tile - const TileLayer &l0 = f.special_tiles[0].layers[0]; + const TileLayer &l0 = v.special_tiles[0].layers[0]; mesh = getExtrudedMesh( extractTexture(f.tiledef_special[0], l0, tsrc) ); diff --git a/src/dummygamedef.h b/src/dummygamedef.h index 5b039158ce..cf864edb0f 100644 --- a/src/dummygamedef.h +++ b/src/dummygamedef.h @@ -11,6 +11,9 @@ #include "craftdef.h" #include "content/mods.h" #include "database/database-dummy.h" +#if CHECK_CLIENT_BUILD() +#include "client/node_visuals.h" +#endif class DummyGameDef : public IGameDef { public: @@ -62,4 +65,8 @@ protected: NodeDefManager *m_nodedef = nullptr; ICraftDefManager *m_craftdef = nullptr; ModStorageDatabase *m_mod_storage_database = nullptr; + +#if CHECK_CLIENT_BUILD() + static NodeVisuals *constructNodeVisuals(ContentFeatures *f) { return new NodeVisuals(f); } +#endif }; diff --git a/src/gui/drawItemStack.cpp b/src/gui/drawItemStack.cpp index 649098fb16..6612ecf640 100644 --- a/src/gui/drawItemStack.cpp +++ b/src/gui/drawItemStack.cpp @@ -15,6 +15,8 @@ #include "client/texturesource.h" #include "client/guiscalingfilter.h" #include "client/item_visuals_manager.h" +#include +#include struct MeshTimeInfo { u64 time; diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index 7e4977b59d..59fe950680 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -6,6 +6,7 @@ #pragma once +#include "constants.h" #include "noise.h" #include "nodedef.h" #include "util/string.h" diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h index 37e09136ef..1363cf5305 100644 --- a/src/mapgen/mg_biome.h +++ b/src/mapgen/mg_biome.h @@ -5,6 +5,7 @@ #pragma once +#include "constants.h" #include "objdef.h" #include "nodedef.h" #include "noise.h" diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 6967b8fa32..d1dd4ac8ce 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -29,15 +29,6 @@ static const u8 rot_to_wallmounted[] = { MapNode */ -void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const -{ - if (f.palette) { - *color = (*f.palette)[param2]; - return; - } - *color = f.color; -} - u8 MapNode::getFaceDir(const NodeDefManager *nodemgr, bool allow_wallmounted) const { diff --git a/src/mapnode.h b/src/mapnode.h index 1640f2a650..32ddf183a3 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -188,14 +188,6 @@ struct alignas(u32) MapNode param2 = p; } - /*! - * Returns the color of the node. - * - * \param f content features of this node - * \param color output, contains the node's color. - */ - void getColor(const ContentFeatures &f, video::SColor *color) const; - inline void setLight(LightBank bank, u8 a_light, ContentLightingFlags f) noexcept { // If node doesn't contain light data, ignore this diff --git a/src/nodedef.cpp b/src/nodedef.cpp index a2d2a95b6b..8a22036c2e 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -5,18 +5,6 @@ #include "nodedef.h" #include "itemdef.h" -#if CHECK_CLIENT_BUILD() -#include "client/mesh.h" -#include "client/shader.h" -#include "client/client.h" -#include "client/renderingengine.h" -#include "client/texturesource.h" -#include "client/tile.h" -#include -#include -#include -#include "client/wieldmesh.h" // createAnimationFrames -#endif #include "log.h" #include "settings.h" #include "nameidmapping.h" @@ -27,9 +15,11 @@ #include "debug.h" #include "gamedef.h" #include "mapnode.h" -#include // Used in applyTextureOverrides() #include #include +#if CHECK_CLIENT_BUILD() +#include "client/node_visuals.h" // ~NodeVisuals +#endif /* NodeBox @@ -309,55 +299,6 @@ void TextureSettings::readSettings() autoscale_mode = AUTOSCALE_DISABLE; } -/* - Texture pool and related -*/ - -#if CHECK_CLIENT_BUILD() -struct PreLoadedTexture { - video::ITexture *texture = nullptr; - u32 texture_id = 0; - u16 texture_layer_idx = 0; - bool used = false; // For debugging -}; - -struct PreLoadedTextures { - std::unordered_map pool; - std::unordered_set missed; // For debugging - - PreLoadedTexture find(const std::string &name); - void add(const std::string &name, const PreLoadedTexture &t); - - void printStats(std::ostream &to) const; -}; - -PreLoadedTexture PreLoadedTextures::find(const std::string &name) -{ - auto it = pool.find(name); - if (it == pool.end()) { - missed.emplace(name); - return {}; - } - it->second.used = true; - return it->second; -} - -void PreLoadedTextures::add(const std::string &name, const PreLoadedTexture &t) -{ - assert(pool.find(name) == pool.end()); - pool[name] = t; -} - -void PreLoadedTextures::printStats(std::ostream &to) const -{ - size_t unused = 0; - for (auto &it : pool) - unused += it.second.used ? 0 : 1; - to << "PreLoadedTextures: " << pool.size() << "\n wasted: " << unused - << " missed: " << missed.size() << std::endl; -} -#endif - /* ContentFeatures @@ -371,14 +312,7 @@ ContentFeatures::ContentFeatures() ContentFeatures::~ContentFeatures() { #if CHECK_CLIENT_BUILD() - for (u16 j = 0; j < 6; j++) { - delete tiles[j].layers[0].frames; - delete tiles[j].layers[1].frames; - } - for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) { - delete special_tiles[j].layers[0].frames; - delete special_tiles[j].layers[1].frames; - } + delete visuals; #endif } @@ -387,12 +321,6 @@ void ContentFeatures::reset() /* Cached stuff */ -#if CHECK_CLIENT_BUILD() - solidness = 2; - visual_solidness = 0; - backface_culling = true; - -#endif has_on_construct = false; has_on_destruct = false; has_after_destruct = false; @@ -410,10 +338,6 @@ void ContentFeatures::reset() groups["dig_immediate"] = 2; drawtype = NDT_NORMAL; mesh.clear(); -#if CHECK_CLIENT_BUILD() - mesh_ptr = nullptr; - minimap_color = video::SColor(0, 0, 0, 0); -#endif visual_scale = 1.0; for (auto &i : tiledef) i = TileDef(); @@ -460,7 +384,6 @@ void ContentFeatures::reset() connect_sides = 0; color = video::SColor(0xFFFFFFFF); palette_name.clear(); - palette = NULL; node_dig_prediction = "air"; move_resistance = 0; liquid_move_physics = false; @@ -725,491 +648,16 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version) } catch (SerializationError &e) {}; } -#if CHECK_CLIENT_BUILD() -struct TileAttribContext { - ITextureSource *tsrc; - PreLoadedTextures *texture_pool; - video::SColor base_color; - const TextureSettings &tsettings; -}; - -struct ShaderIds { - u32 normal; - // Shader that will handle an array texture and texture_layer_idx - u32 with_layers; -}; - -static void fillTileAttribs(TileLayer *layer, TileAttribContext context, - const TileSpec &tile, const TileDef &tiledef, - MaterialType material_type, ShaderIds shader) -{ - auto *tsrc = context.tsrc; - const auto &tsettings = context.tsettings; - - std::string texture_image; - if (!tiledef.name.empty()) { - texture_image = tiledef.name; - if (tsrc->needFilterForMesh()) - texture_image += tsrc->FILTER_FOR_MESH; - } else { - // Tile is empty, nothing to do. - return; - } - - core::dimension2du texture_size; - if (!texture_image.empty()) - texture_size = tsrc->getTextureDimensions(texture_image); - if (!texture_size.Width || !texture_size.Height) - texture_size = {1, 1}; // dummy if there's an error - - // Scale - bool has_scale = tiledef.scale > 0; - bool use_autoscale = tsettings.autoscale_mode == AUTOSCALE_FORCE || - (tsettings.autoscale_mode == AUTOSCALE_ENABLE && !has_scale); - if (use_autoscale) { - float base_size = tsettings.node_texture_size; - float size = std::fmin(texture_size.Width, texture_size.Height); - layer->scale = std::fmax(base_size, size) / base_size; - } else if (has_scale) { - layer->scale = tiledef.scale; - } else { - layer->scale = 1; - } - if (!tile.world_aligned) - layer->scale = 1; - - // Material - layer->material_type = material_type; - layer->material_flags = 0; - if (tiledef.backface_culling) - layer->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; - if (tiledef.animation.type != TAT_NONE) - layer->material_flags |= MATERIAL_FLAG_ANIMATION; - if (tiledef.tileable_horizontal) - layer->material_flags |= MATERIAL_FLAG_TILEABLE_HORIZONTAL; - if (tiledef.tileable_vertical) - layer->material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL; - - // Color - layer->has_color = tiledef.has_color; - if (tiledef.has_color) - layer->color = tiledef.color; - else - layer->color = context.base_color; - - // Animation - if (layer->material_flags & MATERIAL_FLAG_ANIMATION) { - int frame_length_ms = 0; - std::vector frames = createAnimationFrames( - tsrc, tiledef.name, tiledef.animation, frame_length_ms); - if (frames.size() > 1) { - layer->frames = new std::vector(frames); - layer->animation_frame_count = layer->frames->size(); - layer->animation_frame_length_ms = frame_length_ms; - - // Set default texture to first frame (not used practice) - layer->texture = (*layer->frames)[0].texture; - layer->texture_id = (*layer->frames)[0].texture_id; - } else { - layer->material_flags &= ~MATERIAL_FLAG_ANIMATION; - } - } - - if (!(layer->material_flags & MATERIAL_FLAG_ANIMATION)) { - // Grab texture - auto tex = context.texture_pool->find(texture_image); - if (!tex.texture) { - // wasn't pre-loaded: create standard texture on the fly - layer->texture = tsrc->getTexture(texture_image, &layer->texture_id); - } else { - layer->texture = tex.texture; - layer->texture_id = tex.texture_id; - layer->texture_layer_idx = tex.texture_layer_idx; - } - } - - // Decide on shader to use - if (layer->texture) { - layer->shader_id = (layer->texture->getType() == video::ETT_2D_ARRAY) ? - shader.with_layers : shader.normal; - } -} - -void ContentFeatures::preUpdateTextures(ITextureSource *tsrc, - std::unordered_set &pool, const TextureSettings &tsettings) -{ - // Find out the exact texture strings this node might use, and put them into the pool - // (this should match updateTextures, but it's not the end of the world if - // a mismatch occurs) - std::string append; - if (tsrc->needFilterForMesh()) - append = ITextureSource::FILTER_FOR_MESH; - std::string append_overlay = append, append_special = append; - bool use = true, use_overlay = true, use_special = true; - - if (drawtype == NDT_ALLFACES_OPTIONAL) { - use_special = (tsettings.leaves_style == LEAVES_SIMPLE); - use = !use_special; - if (tsettings.leaves_style == LEAVES_OPAQUE) - append.insert(0, "^[noalpha"); - } - - const auto &consider_tile = [&] (const TileDef &def, const std::string &append) { - // Animations are chopped into frames later, so we won't actually need - // the source texture - if (!def.name.empty() && def.animation.type == TAT_NONE) { - pool.insert(def.name + append); - } - }; - - for (u32 j = 0; j < 6; j++) { - if (use) - consider_tile(tiledef[j], append); - } - for (u32 j = 0; j < 6; j++) { - if (use_overlay) - consider_tile(tiledef_overlay[j], append_overlay); - } - for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) { - if (use_special) - consider_tile(tiledef_special[j], append_special); - } -} - -static bool isWorldAligned(AlignStyle style, WorldAlignMode mode, NodeDrawType drawtype) -{ - if (style == ALIGN_STYLE_WORLD) - return true; - if (mode == WORLDALIGN_DISABLE) - return false; - if (style == ALIGN_STYLE_USER_DEFINED) - return true; - if (drawtype == NDT_NORMAL) - return mode >= WORLDALIGN_FORCE; - if (drawtype == NDT_NODEBOX) - return mode >= WORLDALIGN_FORCE_NODEBOX; - return false; -} - -/// @return maximum number of layers in array textures we can use (0 if unsupported) -static size_t getArrayTextureMax(IShaderSource *shdsrc) -{ - auto *driver = RenderingEngine::get_video_driver(); - // needs to support creating array textures - if (!driver->queryFeature(video::EVDF_TEXTURE_2D_ARRAY)) - return 0; - // must support sampling from them - if (!shdsrc->supportsSampler2DArray()) - return 0; - // shadow shaders can't handle array textures yet (TODO) - if (g_settings->getBool("enable_dynamic_shadows")) - return 0; - - u32 n = driver->getLimits().MaxArrayTextureImages; - constexpr u32 type_max = std::numeric_limits::max(); - n = std::min(n, type_max); - n = std::min(n, g_settings->getU32("array_texture_max")); - return n; -} - -void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, - Client *client, PreLoadedTextures *texture_pool, - const TextureSettings &tsettings) -{ - // Figure out the actual tiles to use - TileDef tdef[6]; - for (u32 j = 0; j < 6; j++) { - tdef[j] = tiledef[j]; - if (tdef[j].name.empty()) { - tdef[j].name = "no_texture.png"; - tdef[j].backface_culling = false; - } - } - // also the overlay tiles - TileDef tdef_overlay[6]; - for (u32 j = 0; j < 6; j++) - tdef_overlay[j] = tiledef_overlay[j]; - // also the special tiles - TileDef tdef_spec[6]; - for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) { - tdef_spec[j] = tiledef_special[j]; - } - - bool is_liquid = false; - - MaterialType material_type = alpha_mode_to_material_type(alpha); - - switch (drawtype) { - default: - case NDT_NORMAL: - solidness = 2; - break; - case NDT_AIRLIKE: - solidness = 0; - break; - case NDT_LIQUID: - if (!tsettings.translucent_liquids) - alpha = ALPHAMODE_OPAQUE; - solidness = 1; - is_liquid = true; - break; - case NDT_FLOWINGLIQUID: - solidness = 0; - if (!tsettings.translucent_liquids) - alpha = ALPHAMODE_OPAQUE; - is_liquid = true; - break; - case NDT_GLASSLIKE: - solidness = 0; - visual_solidness = 1; - break; - case NDT_GLASSLIKE_FRAMED: - solidness = 0; - visual_solidness = 1; - break; - case NDT_GLASSLIKE_FRAMED_OPTIONAL: - solidness = 0; - visual_solidness = 1; - drawtype = tsettings.connected_glass ? NDT_GLASSLIKE_FRAMED : NDT_GLASSLIKE; - break; - case NDT_ALLFACES: - solidness = 0; - visual_solidness = 1; - break; - case NDT_ALLFACES_OPTIONAL: - if (tsettings.leaves_style == LEAVES_FANCY) { - drawtype = NDT_ALLFACES; - solidness = 0; - visual_solidness = 1; - } else if (tsettings.leaves_style == LEAVES_SIMPLE) { - for (u32 j = 0; j < 6; j++) { - if (!tdef_spec[j].name.empty()) - tdef[j].name = tdef_spec[j].name; - } - drawtype = NDT_GLASSLIKE; - solidness = 0; - visual_solidness = 1; - } else { - if (waving >= 1) { - // waving nodes must make faces so there are no gaps - drawtype = NDT_ALLFACES; - solidness = 0; - visual_solidness = 1; - } else { - drawtype = NDT_NORMAL; - solidness = 2; - } - for (TileDef &td : tdef) - td.name += std::string("^[noalpha"); - } - if (waving >= 1) - material_type = TILE_MATERIAL_WAVING_LEAVES; - break; - case NDT_PLANTLIKE: - solidness = 0; - if (waving >= 1) - material_type = TILE_MATERIAL_WAVING_PLANTS; - break; - case NDT_FIRELIKE: - solidness = 0; - break; - case NDT_MESH: - case NDT_NODEBOX: - solidness = 0; - if (waving == 1) { - material_type = TILE_MATERIAL_WAVING_PLANTS; - } else if (waving == 2) { - material_type = TILE_MATERIAL_WAVING_LEAVES; - } else if (waving == 3) { - material_type = alpha == ALPHAMODE_OPAQUE ? - TILE_MATERIAL_WAVING_LIQUID_OPAQUE : (alpha == ALPHAMODE_CLIP ? - TILE_MATERIAL_WAVING_LIQUID_BASIC : TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT); - } - break; - case NDT_TORCHLIKE: - case NDT_SIGNLIKE: - case NDT_FENCELIKE: - case NDT_RAILLIKE: - solidness = 0; - break; - case NDT_PLANTLIKE_ROOTED: - solidness = 2; - break; - } - - if (is_liquid) { - if (waving == 3) { - material_type = alpha == ALPHAMODE_OPAQUE ? - TILE_MATERIAL_WAVING_LIQUID_OPAQUE : (alpha == ALPHAMODE_CLIP ? - TILE_MATERIAL_WAVING_LIQUID_BASIC : TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT); - } else { - material_type = alpha == ALPHAMODE_OPAQUE ? TILE_MATERIAL_LIQUID_OPAQUE : - TILE_MATERIAL_LIQUID_TRANSPARENT; - } - } - - const bool texture_2d_array = getArrayTextureMax(shdsrc) > 1; - const auto &getNodeShader = [&] (MaterialType my_material, NodeDrawType my_drawtype) { - ShaderIds ret; - ret.normal = shdsrc->getShader("nodes_shader", my_material, my_drawtype); - // need to avoid generating the shader if unsupported - if (texture_2d_array) - ret.with_layers = shdsrc->getShader("nodes_shader", my_material, my_drawtype, true); - return ret; - }; - - ShaderIds tile_shader = getNodeShader(material_type, drawtype); - - MaterialType overlay_material = material_type_with_alpha(material_type); - - ShaderIds overlay_shader = getNodeShader(overlay_material, drawtype); - - // minimap pixel color = average color of top tile - if (tsettings.enable_minimap && drawtype != NDT_AIRLIKE && !tdef[0].name.empty()) - { - if (!tdef_overlay[0].name.empty()) { - // Merge overlay and base texture - std::string combined = tdef[0].name + "^(" + tdef_overlay[0].name + ")"; - minimap_color = tsrc->getTextureAverageColor(combined); - } else { - minimap_color = tsrc->getTextureAverageColor(tdef[0].name); - } - } - - // Tiles (fill in f->tiles[]) - bool any_polygon_offset = false; - TileAttribContext tac{tsrc, texture_pool, color, tsettings}; - - for (u16 j = 0; j < 6; j++) { - tiles[j].world_aligned = isWorldAligned(tdef[j].align_style, - tsettings.world_aligned_mode, drawtype); - fillTileAttribs(&tiles[j].layers[0], tac, tiles[j], tdef[j], - material_type, tile_shader); - if (!tdef_overlay[j].name.empty()) { - tdef_overlay[j].backface_culling = tdef[j].backface_culling; - fillTileAttribs(&tiles[j].layers[1], tac, tiles[j], tdef_overlay[j], - overlay_material, overlay_shader); - } - - tiles[j].layers[0].need_polygon_offset = !tiles[j].layers[1].empty(); - any_polygon_offset |= tiles[j].layers[0].need_polygon_offset; - } - - if (drawtype == NDT_MESH && any_polygon_offset) { - // Our per-tile polygon offset enablement workaround works fine for normal - // nodes and anything else, where we know that different tiles are different - // faces that couldn't possibly conflict with each other. - // We can't assume this for mesh nodes, so apply it to all tiles (= materials) - // then. - for (u16 j = 0; j < 6; j++) - tiles[j].layers[0].need_polygon_offset = true; - } - - MaterialType special_material = material_type; - if (drawtype == NDT_PLANTLIKE_ROOTED) { - if (waving == 1) - special_material = TILE_MATERIAL_WAVING_PLANTS; - else if (waving == 2) - special_material = TILE_MATERIAL_WAVING_LEAVES; - } - - ShaderIds special_shader = getNodeShader(special_material, drawtype); - - // Special tiles (fill in f->special_tiles[]) - for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) { - fillTileAttribs(&special_tiles[j].layers[0], tac, - special_tiles[j], tdef_spec[j], special_material, special_shader); - } - - if (param_type_2 == CPT2_COLOR || - param_type_2 == CPT2_COLORED_FACEDIR || - param_type_2 == CPT2_COLORED_4DIR || - param_type_2 == CPT2_COLORED_WALLMOUNTED || - param_type_2 == CPT2_COLORED_DEGROTATE) - palette = tsrc->getPalette(palette_name); - -} - -void ContentFeatures::updateMesh(Client *client, const TextureSettings &tsettings) -{ - auto *manip = client->getSceneManager()->getMeshManipulator(); - (void)tsettings; - - if (drawtype == NDT_MESH && !mesh.empty()) { - // Note: By freshly reading, we get an unencumbered mesh. - if (scene::IMesh *src_mesh = client->getMesh(mesh)) { - bool apply_bs = false; - 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; - manip->recalculateNormals(mesh_ptr, true, false); - } - } else { - mesh_ptr = nullptr; - } - } -} - -void ContentFeatures::collectMaterials(std::vector &leaves_materials) -{ - if (drawtype == NDT_AIRLIKE) - return; - - for (u16 j = 0; j < 6; j++) { - auto &l = tiles[j].layers; - if (!l[0].empty() && l[0].material_type == TILE_MATERIAL_WAVING_LEAVES) - leaves_materials.push_back(l[0].shader_id); - if (!l[1].empty() && l[1].material_type == TILE_MATERIAL_WAVING_LEAVES) - leaves_materials.push_back(l[1].shader_id); - } -} -#endif - /* NodeDefManager */ - - NodeDefManager::NodeDefManager() { clear(); } - -NodeDefManager::~NodeDefManager() -{ -#if CHECK_CLIENT_BUILD() - for (ContentFeatures &f : m_content_features) { - if (f.mesh_ptr) - f.mesh_ptr->drop(); - } -#endif -} - - void NodeDefManager::clear() { m_content_features.clear(); @@ -1648,101 +1096,11 @@ void NodeDefManager::applyTextureOverrides(const std::vector &o } } -#if CHECK_CLIENT_BUILD() -void NodeDefManager::updateTextures(IGameDef *gamedef, void *progress_callback_args) +void NodeDefManager::applyFunction(const std::function &function) { - infostream << "NodeDefManager::updateTextures(): Updating " - "textures in node definitions" << std::endl; - - Client *client = (Client *)gamedef; - ITextureSource *tsrc = client->tsrc(); - IShaderSource *shdsrc = client->getShaderSource(); - TextureSettings tsettings; - tsettings.readSettings(); - - tsrc->setImageCaching(true); - const u32 size = m_content_features.size(); - - /* collect all textures we might use */ - std::unordered_set pool; - for (u32 i = 0; i < size; i++) { - ContentFeatures *f = &(m_content_features[i]); - f->preUpdateTextures(tsrc, pool, tsettings); - } - - /* texture pre-loading stage */ - const size_t arraymax = getArrayTextureMax(shdsrc); - // Group by size - std::unordered_map> sizes; - if (arraymax > 1) { - infostream << "Using array textures with " << arraymax << " layers" << std::endl; - size_t i = 0; - for (auto &image : pool) { - core::dimension2du dim = tsrc->getTextureDimensions(image); - client->showUpdateProgressTexture(progress_callback_args, - 0.33333f * ++i / pool.size()); - if (!dim.Width || !dim.Height) // error - continue; - sizes[v2u32(dim)].emplace_back(image); - } - } - - // create array textures as far as possible - size_t num_preloadable = 0, preload_progress = 0; - for (auto &it : sizes) { - if (it.second.size() < 2) - continue; - num_preloadable += it.second.size(); - } - PreLoadedTextures plt; - const auto &doBunch = [&] (const std::vector &bunch) { - PreLoadedTexture t; - t.texture = tsrc->addArrayTexture(bunch, &t.texture_id); - preload_progress += bunch.size(); - client->showUpdateProgressTexture(progress_callback_args, - 0.33333f + 0.33333f * preload_progress / num_preloadable); - if (t.texture) { - // Success: all of the images in this bunch can now refer to this texture - for (size_t idx = 0; idx < bunch.size(); idx++) { - t.texture_layer_idx = idx; - plt.add(bunch[idx], t); - } - } - }; - for (auto &it : sizes) { - if (it.second.size() < 2) - continue; - std::vector bunch; - for (auto &image : it.second) { - bunch.emplace_back(image); - if (bunch.size() == arraymax) { - doBunch(bunch); - bunch.clear(); - } - } - if (!bunch.empty()) - doBunch(bunch); - } - // note that standard textures aren't preloaded - - /* final step */ - for (u32 i = 0; i < size; i++) { - ContentFeatures *f = &(m_content_features[i]); - f->updateTextures(tsrc, shdsrc, client, &plt, tsettings); - f->updateMesh(client, tsettings); - f->collectMaterials(m_leaves_materials); - - client->showUpdateProgressTexture(progress_callback_args, - 0.66666f + 0.33333f * i / size); - } - SORT_AND_UNIQUE(m_leaves_materials); - verbosestream << "m_leaves_materials.size() = " << m_leaves_materials.size() - << std::endl; - - plt.printStats(infostream); - tsrc->setImageCaching(false); + for (ContentFeatures &f : m_content_features) + function(f); } -#endif void NodeDefManager::serialize(std::ostream &os, u16 protocol_version) const { diff --git a/src/nodedef.h b/src/nodedef.h index bb2852d402..01e6e92534 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -4,23 +4,14 @@ #pragma once -#include "irrlichttypes_bloated.h" +#include #include #include #include // shared_ptr -#include #include "mapnode.h" #include "nameidmapping.h" -#if CHECK_CLIENT_BUILD() -#include "client/tile.h" -#include -#include -class Client; -struct PreLoadedTextures; -#endif #include "itemgroup.h" #include "sound.h" // SoundSpec -#include "constants.h" // BS #include "texture_override.h" // TextureOverride #include "tileanimation.h" #include "util/pointabilities.h" @@ -33,6 +24,9 @@ class NodeResolver; #if BUILD_UNITTESTS class TestSchematic; #endif +#if CHECK_CLIENT_BUILD() +struct NodeVisuals; +#endif enum ContentParamType : u8 { @@ -261,25 +255,6 @@ enum AlphaMode : u8 { AlphaMode_END // Dummy for validity check }; -#if CHECK_CLIENT_BUILD() -/** - * @brief get fitting material type for an alpha mode - */ -static inline MaterialType alpha_mode_to_material_type(AlphaMode mode) -{ - switch (mode) { - case ALPHAMODE_BLEND: - return TILE_MATERIAL_ALPHA; - case ALPHAMODE_OPAQUE: - return TILE_MATERIAL_OPAQUE; - case ALPHAMODE_CLIP: - default: - return TILE_MATERIAL_BASIC; - } -} -#endif - - /* Stand-alone definition of a TileSpec (basically a server-side TileSpec) */ @@ -321,20 +296,6 @@ struct ContentFeatures // write checks that depend directly on the protocol version instead. static const u8 CONTENTFEATURES_VERSION = 13; - /* - Cached stuff - */ -#if CHECK_CLIENT_BUILD() - // 0 1 2 3 4 5 - // up down right left back front - TileSpec tiles[6]; - // Special tiles - TileSpec special_tiles[CF_SPECIAL_COUNT]; - u8 solidness; // Used when choosing which face is drawn - u8 visual_solidness; // When solidness=0, this tells how it looks like - bool backface_culling; -#endif - // Server-side cached callback existence for fast skipping bool has_on_construct; bool has_on_destruct; @@ -360,10 +321,6 @@ struct ContentFeatures enum NodeDrawType drawtype; std::string mesh; -#if CHECK_CLIENT_BUILD() - scene::SMesh *mesh_ptr; // mesh in case of mesh node - video::SColor minimap_color; -#endif float visual_scale; // Misc. scale parameter TileDef tiledef[6]; // These will be drawn over the base tiles. @@ -373,7 +330,6 @@ struct ContentFeatures // The color of the node. video::SColor color; std::string palette_name; - std::vector *palette; // Used for waving leaves/plants u8 waving; // for NDT_CONNECTED pairing @@ -388,6 +344,14 @@ struct ContentFeatures // Maximum value for leveled nodes u8 leveled_max; + // --- CLIENT ONLY --- + +#if CHECK_CLIENT_BUILD() + // The Client class fills this for its NodeDefManager using fillNodeVisuals, + // thus for ContentFeatures of a Client it is not a nullptr. + NodeVisuals *visuals = nullptr; +#endif + // --- LIGHTING-RELATED --- bool light_propagates; @@ -524,16 +488,6 @@ struct ContentFeatures return itemgroup_get(groups, group); } -#if CHECK_CLIENT_BUILD() - void preUpdateTextures(ITextureSource *tsrc, - std::unordered_set &pool, const TextureSettings &tsettings); - void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc, - Client *client, PreLoadedTextures *texture_pool, - const TextureSettings &tsettings); - void updateMesh(Client *client, const TextureSettings &tsettings); - void collectMaterials(std::vector &leaves_materials); -#endif - private: void setAlphaFromLegacy(u8 legacy_alpha); @@ -559,7 +513,6 @@ public: * \ref CONTENT_AIR, \ref CONTENT_UNKNOWN and \ref CONTENT_IGNORE. */ NodeDefManager(); - ~NodeDefManager(); /*! * Returns the properties for the given content type. @@ -700,18 +653,21 @@ public: */ void applyTextureOverrides(const std::vector &overrides); -#if CHECK_CLIENT_BUILD() /*! - * Loads textures and shaders required for - * rendering the nodes. - * @param gamedef must be a Client. - * @param progress_cbk called each time a node is loaded. Arguments: - * `progress_cbk_args`, number of loaded ContentFeatures, number of - * total ContentFeatures. - * @param progress_cbk_args passed to the callback function + * Applies a function to all Content Features. + * Clients need this to make use of the visuals field. + * @param function to apply */ - void updateTextures(IGameDef *gamedef, void *progress_cbk_args); -#endif + void applyFunction(const std::function &function); + + /*! + * Returns the amount of managed content IDs. + * Invalid and removed IDs are also counted. + */ + inline u32 size() const + { + return m_content_features.size(); + } /*! * Writes the content of this manager to the given output stream. diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index c7091dc921..c04a545781 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -24,6 +24,10 @@ #include #include "mapgen/treegen.h" +#if CHECK_CLIENT_BUILD() +#include "client/node_visuals.h" +#endif + struct EnumString es_TileAnimationType[] = { {TAT_NONE, "none"}, @@ -1079,8 +1083,10 @@ void push_content_features(lua_State *L, const ContentFeatures &c) lua_setfield(L, -2, "mesh"); } #if CHECK_CLIENT_BUILD() - push_ARGB8(L, c.minimap_color); // I know this is not set-able w/ register_node, - lua_setfield(L, -2, "minimap_color"); // but the people need to know! + if (c.visuals) { + push_ARGB8(L, c.visuals->minimap_color); // I know this is not set-able w/ register_node, + lua_setfield(L, -2, "minimap_color"); // but the people need to know! + } #endif lua_pushnumber(L, c.visual_scale); lua_setfield(L, -2, "visual_scale"); @@ -1093,8 +1099,12 @@ void push_content_features(lua_State *L, const ContentFeatures &c) lua_pushstring(L, c.palette_name.c_str()); lua_setfield(L, -2, "palette_name"); - push_palette(L, c.palette); - lua_setfield(L, -2, "palette"); +#if CHECK_CLIENT_BUILD() + if (c.visuals) { + push_palette(L, c.visuals->palette); + lua_setfield(L, -2, "palette"); + } +#endif } lua_pushnumber(L, c.waving); lua_setfield(L, -2, "waving"); diff --git a/src/unittest/test_content_mapblock.cpp b/src/unittest/test_content_mapblock.cpp index 3f1263fe67..bfd2d1f426 100644 --- a/src/unittest/test_content_mapblock.cpp +++ b/src/unittest/test_content_mapblock.cpp @@ -13,6 +13,7 @@ #include "client/content_mapblock.h" #include "client/mapblock_mesh.h" #include "client/meshgen/collector.h" +#include "client/node_visuals.h" #include "mesh_compare.h" namespace { @@ -27,13 +28,27 @@ public: return const_cast(m_nodedef); } - content_t registerNode(ItemDefinition itemdef, ContentFeatures nodedef) { + // ContentFeatures that doesn't destroy the visuals + // Needed because nodedef.set(feature) creates a copy of the ContentFeatures and since + // the NodeDefManager destructs its ContentFeatures, this prevents double free. + // Should only be used if the visuals get freed somewhere else. + struct CContentFeatures : public ContentFeatures { + ~CContentFeatures() { visuals = nullptr; } + }; + + content_t registerNode(ItemDefinition itemdef, const CContentFeatures &nodedef) { item_mgr()->registerItem(itemdef); return node_mgr()->set(nodedef.name, nodedef); } void finalize() { node_mgr()->resolveCrossrefs(); + + // Need to fill node visuals for predefined nodes + node_mgr()->applyFunction([] (ContentFeatures &f) { + if (!f.visuals) + f.visuals = constructNodeVisuals(&f); + }); } MeshMakeData makeSingleNodeMMD(bool smooth_lighting = true) @@ -57,14 +72,15 @@ public: itemdef.name = "test:" + name; itemdef.description = name; - ContentFeatures f; + CContentFeatures f; + f.visuals = constructNodeVisuals(&f); f.name = itemdef.name; f.drawtype = NDT_NORMAL; - f.solidness = 2; + f.visuals->solidness = 2; f.alpha = ALPHAMODE_OPAQUE; for (TileDef &tiledef : f.tiledef) tiledef.name = name + ".png"; - for (TileSpec &tile : f.tiles) + for (TileSpec &tile : f.visuals->tiles) tile.layers[0].texture_id = texture; return registerNode(itemdef, f); @@ -77,10 +93,11 @@ public: itemdef.name = "test:" + name + "_source"; itemdef.description = name; - ContentFeatures f; + CContentFeatures f; + f.visuals = constructNodeVisuals(&f); f.name = itemdef.name; f.drawtype = NDT_LIQUID; - f.solidness = 1; + f.visuals->solidness = 1; f.alpha = ALPHAMODE_BLEND; f.light_propagates = true; f.param_type = CPT_LIGHT; @@ -91,7 +108,7 @@ public: f.liquid_alternative_flowing = "test:" + name + "_flowing"; for (TileDef &tiledef : f.tiledef) tiledef.name = name + ".png"; - for (TileSpec &tile : f.tiles) + for (TileSpec &tile : f.visuals->tiles) tile.layers[0].texture_id = texture; return registerNode(itemdef, f); @@ -104,10 +121,11 @@ public: itemdef.name = "test:" + name + "_flowing"; itemdef.description = name; - ContentFeatures f; + CContentFeatures f; + f.visuals = constructNodeVisuals(&f); f.name = itemdef.name; f.drawtype = NDT_FLOWINGLIQUID; - f.solidness = 0; + f.visuals->solidness = 0; f.alpha = ALPHAMODE_BLEND; f.light_propagates = true; f.param_type = CPT_LIGHT; @@ -118,8 +136,8 @@ public: f.liquid_alternative_flowing = "test:" + name + "_flowing"; f.tiledef_special[0].name = name + "_top.png"; f.tiledef_special[1].name = name + "_side.png"; - f.special_tiles[0].layers[0].texture_id = texture_top; - f.special_tiles[1].layers[0].texture_id = texture_side; + f.visuals->special_tiles[0].layers[0].texture_id = texture_top; + f.visuals->special_tiles[1].layers[0].texture_id = texture_side; return registerNode(itemdef, f); }