From 3e59b186d3ed6eaf24be56a8e89facb6c457084f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Jul 2025 18:51:53 +0300 Subject: [PATCH 01/10] fix custom models shading flag & add more properties to vcm --- res/devtools/syntax/vcm.toml | 3 +++ src/coders/vcm.cpp | 25 +++++++++++++++++++++++-- src/graphics/commons/Model.hpp | 10 +++++----- src/graphics/render/BlocksRenderer.cpp | 6 +++--- src/graphics/render/ModelBatch.cpp | 6 +++--- src/graphics/render/ModelsGenerator.cpp | 6 +++--- 6 files changed, 40 insertions(+), 16 deletions(-) diff --git a/res/devtools/syntax/vcm.toml b/res/devtools/syntax/vcm.toml index ca05b6a1..524fdc80 100644 --- a/res/devtools/syntax/vcm.toml +++ b/res/devtools/syntax/vcm.toml @@ -1,3 +1,6 @@ language = "VCM" extensions = ["vcm"] line-comment-start = "#" +keywords = [ + "on", "off" +] diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index 20c50982..a12524c0 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -19,10 +19,15 @@ static const std::unordered_map side_indices { {"east", 5}, }; +static bool to_boolean(const xml::Attribute& attr) { + return attr.getText() != "off"; +} + static void perform_rect(const xmlelement& root, model::Model& model) { auto from = root.attr("from").asVec3(); auto right = root.attr("right").asVec3(); auto up = root.attr("up").asVec3(); + bool shading = true; right *= -1; from -= right; @@ -37,6 +42,10 @@ static void perform_rect(const xmlelement& root, model::Model& model) { region.scale(root.attr("region-scale").asVec2()); } + if (root.has("shading")) { + shading = to_boolean(root.attr("shading")); + } + auto flip = root.attr("flip", "").getText(); if (flip == "h") { std::swap(region.u1, region.u2); @@ -48,7 +57,7 @@ static void perform_rect(const xmlelement& root, model::Model& model) { from -= up; } std::string texture = root.attr("texture", "$0").getText(); - auto& mesh = model.addMesh(texture); + auto& mesh = model.addMesh(texture, shading); auto normal = glm::cross(glm::normalize(right), glm::normalize(up)); mesh.addRect( @@ -75,8 +84,20 @@ static void perform_box(const xmlelement& root, model::Model& model) { auto center = (from + to) * 0.5f; auto halfsize = (to - from) * 0.5f; + bool shading = true; std::string texfaces[6] {"$0","$1","$2","$3","$4","$5"}; + if (root.has("texture")) { + auto texture = root.attr("texture").getText(); + for (int i = 0; i < 6; i++) { + texfaces[i] = texture; + } + } + + if (root.has("shading")) { + shading = to_boolean(root.attr("shading")); + } + for (const auto& elem : root.getElements()) { if (elem->getTag() == "part") { // todo: replace by expression parsing @@ -120,7 +141,7 @@ static void perform_box(const xmlelement& root, model::Model& model) { } bool enabled[6] {}; enabled[i] = true; - auto& mesh = model.addMesh(texfaces[i]); + auto& mesh = model.addMesh(texfaces[i], shading); mesh.addBox(center, halfsize, regions, enabled); } } diff --git a/src/graphics/commons/Model.hpp b/src/graphics/commons/Model.hpp index 0aed1b76..73bee359 100644 --- a/src/graphics/commons/Model.hpp +++ b/src/graphics/commons/Model.hpp @@ -16,7 +16,7 @@ namespace model { struct Mesh { std::string texture; std::vector vertices; - bool lighting = true; + bool shading = true; void addPlane( const glm::vec3& pos, @@ -54,14 +54,14 @@ namespace model { /// @brief Add mesh to the model /// @param texture texture name /// @return writeable Mesh - Mesh& addMesh(const std::string& texture) { + Mesh& addMesh(const std::string& texture, bool shading = true) { for (auto& mesh : meshes) { - if (mesh.texture == texture) { + if (mesh.texture == texture && mesh.shading == shading) { return mesh; } } - meshes.push_back({texture, {}}); - return meshes[meshes.size()-1]; + meshes.push_back({texture, {}, shading}); + return meshes[meshes.size() - 1]; } /// @brief Remove all empty meshes void clean(); diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 2fedd15b..483ed89e 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -341,7 +341,7 @@ void BlocksRenderer::blockCustomModel( const auto& vcoord = vertex.coord - 0.5f; glm::vec4 aoColor {1.0f, 1.0f, 1.0f, 1.0f}; - if (ao) { + if (mesh.shading && ao) { auto p = coord + vcoord.x * X + vcoord.y * Y + vcoord.z * Z + r * 0.5f + t * 0.5f + n * 0.5f; aoColor = pickSoftLight(p.x, p.y, p.z, glm::ivec3(r), glm::ivec3(t)); @@ -350,9 +350,9 @@ void BlocksRenderer::blockCustomModel( coord + vcoord.x * X + vcoord.y * Y + vcoord.z * Z, vertex.uv.x, vertex.uv.y, - glm::vec4(d, d, d, d) * aoColor, + mesh.shading ? (glm::vec4(d, d, d, d) * aoColor) : glm::vec4(1, 1, 1, d), n, - 0.0f + mesh.shading ? 0.0f : 1.0 ); indexBuffer[indexCount++] = vertexOffset++; } diff --git a/src/graphics/render/ModelBatch.cpp b/src/graphics/render/ModelBatch.cpp index 61123232..cefe94ac 100644 --- a/src/graphics/render/ModelBatch.cpp +++ b/src/graphics/render/ModelBatch.cpp @@ -70,7 +70,7 @@ void ModelBatch::draw( const auto& vertexData = mesh.vertices.data(); glm::vec4 lights(1, 1, 1, 0); - if (mesh.lighting) { + if (mesh.shading) { glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); gpos += lightsOffset; lights = MainBatch::sampleLight(gpos, chunks, backlight); @@ -81,7 +81,7 @@ void ModelBatch::draw( const auto vert = vertexData[i * 3 + j]; float d = 1.0f; auto norm = rotation * vert.normal; - if (mesh.lighting) { + if (mesh.shading) { d = glm::dot(norm, SUN_VECTOR); d = 0.8f + d * 0.2f; } @@ -91,7 +91,7 @@ void ModelBatch::draw( lights * d, tint, norm, - mesh.lighting ? 0.0f : 1.0f + mesh.shading ? 0.0f : 1.0f ); } } diff --git a/src/graphics/render/ModelsGenerator.cpp b/src/graphics/render/ModelsGenerator.cpp index 230c4804..f1d878f2 100644 --- a/src/graphics/render/ModelsGenerator.cpp +++ b/src/graphics/render/ModelsGenerator.cpp @@ -97,7 +97,7 @@ model::Model ModelsGenerator::fromCustom( auto model = model::Model(); for (size_t i = 0; i < modelBoxes.size(); i++) { auto& mesh = model.addMesh("blocks:"); - mesh.lighting = lighting; + mesh.shading = lighting; UVRegion boxtexfaces[6] = { get_region_for(modelTextures[i * 6 + 5], assets), get_region_for(modelTextures[i * 6 + 4], assets), @@ -132,7 +132,7 @@ model::Model ModelsGenerator::fromCustom( norm = glm::normalize(norm); auto& mesh = model.addMesh(texture); - mesh.lighting = lighting; + mesh.shading = lighting; auto reg = get_region_for(texture, assets); mesh.vertices.push_back({v0, glm::vec2(reg.u1, reg.v1), norm}); @@ -163,7 +163,7 @@ model::Model ModelsGenerator::generate( return model; } for (auto& mesh : model.meshes) { - mesh.lighting = !blockDef.shadeless; + mesh.shading = !blockDef.shadeless; switch (blockDef.model.type) { case BlockModelType::AABB: { glm::vec3 size = blockDef.hitboxes.at(0).size(); From 01181b64039ab8c272f59254cc66bb4f7e44b020 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Jul 2025 21:27:36 +0300 Subject: [PATCH 02/10] add begin(), end() to util::stack_vector --- src/util/stack_vector.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/util/stack_vector.hpp b/src/util/stack_vector.hpp index caa754cf..896002ce 100644 --- a/src/util/stack_vector.hpp +++ b/src/util/stack_vector.hpp @@ -94,6 +94,22 @@ namespace util { bool full() const { return size_ == capacity; } + + auto begin() { + return data_; + } + + auto end() { + return data_ + size_; + } + + auto begin() const { + return data_; + } + + auto end() const { + return data_ + size_; + } private: T data_[capacity]; int size_; From 5478d121f030901cc94129e2c90f3175f70c5b9c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Jul 2025 21:28:57 +0300 Subject: [PATCH 03/10] add variants (WIP) --- src/assets/AssetsLoader.cpp | 23 ++++-- src/content/ContentBuilder.cpp | 4 +- src/content/loading/BlockLoader.cpp | 88 ++++++++++++---------- src/core_defs.cpp | 10 +-- src/frontend/ContentGfxCache.cpp | 35 +++++++-- src/frontend/ContentGfxCache.hpp | 6 +- src/graphics/render/BlockWrapsRenderer.cpp | 2 +- src/graphics/render/BlocksPreview.cpp | 8 +- src/graphics/render/BlocksRenderer.cpp | 86 ++++++++++++--------- src/graphics/render/BlocksRenderer.hpp | 23 +++--- src/graphics/render/ModelsGenerator.cpp | 76 +++++++++++-------- src/graphics/render/ModelsGenerator.hpp | 5 ++ src/logic/scripting/lua/libs/libblock.cpp | 5 +- src/voxels/Block.cpp | 30 ++++---- src/voxels/Block.hpp | 42 ++++++++--- src/voxels/VoxelsVolume.hpp | 9 +++ 16 files changed, 275 insertions(+), 177 deletions(-) diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index fd399ecd..971e754d 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -227,6 +227,17 @@ void AssetsLoader::processPreloadConfigs(const Content* content) { } } +static void add_variant(AssetsLoader& loader, const Variant& variant) { + if (!variant.model.name.empty() && + variant.model.name.find(':') == std::string::npos) { + loader.add( + AssetType::MODEL, + MODELS_FOLDER + "/" + variant.model.name, + variant.model.name + ); + } +} + void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { loader.processPreloadConfigs(content); if (content) { @@ -261,13 +272,11 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { } } for (const auto& [_, def] : content->blocks.getDefs()) { - if (!def->model.name.empty() && - def->model.name.find(':') == std::string::npos) { - loader.add( - AssetType::MODEL, - MODELS_FOLDER + "/" + def->model.name, - def->model.name - ); + add_variant(loader, def->defaults); + if (def->variants) { + for (const auto& variant : def->variants->variants) { + add_variant(loader, variant); + } } } for (const auto& [_, def] : content->items.getDefs()) { diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index c8b599ea..e7b8df3b 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -28,7 +28,7 @@ std::unique_ptr ContentBuilder::build() { // Generating runtime info def.rt.id = blockDefsIndices.size(); def.rt.emissive = *reinterpret_cast(def.emission); - def.rt.solid = def.model.type == BlockModelType::BLOCK; + def.rt.solid = def.defaults.model.type == BlockModelType::BLOCK; // FIXME def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1; const float EPSILON = 0.01f; @@ -50,7 +50,7 @@ std::unique_ptr ContentBuilder::build() { } blockDefsIndices.push_back(&def); - groups->insert(def.drawGroup); + groups->insert(def.defaults.drawGroup); // FIXME } std::vector itemDefsIndices; diff --git a/src/content/loading/BlockLoader.cpp b/src/content/loading/BlockLoader.cpp index 9569301b..1936f94e 100644 --- a/src/content/loading/BlockLoader.cpp +++ b/src/content/loading/BlockLoader.cpp @@ -40,6 +40,49 @@ static void perform_user_block_fields( layout = StructLayout::create(fields); } +static void load_variant( + Variant& variant, const dv::value& root, const std::string& name +) { + // block texturing + if (root.has("texture")) { + const auto& texture = root["texture"].asString(); + for (uint i = 0; i < 6; i++) { + variant.textureFaces[i] = texture; + } + } else if (root.has("texture-faces")) { + const auto& texarr = root["texture-faces"]; + for (uint i = 0; i < 6; i++) { + variant.textureFaces[i] = texarr[i].asString(); + } + } + + // block model + auto& model = variant.model; + std::string modelTypeName = BlockModelTypeMeta.getNameString(model.type); + root.at("model").get(modelTypeName); + root.at("model-name").get(model.name); + if (BlockModelTypeMeta.getItem(modelTypeName, model.type)) { + if (model.type == BlockModelType::CUSTOM && model.customRaw == nullptr) { + if (root.has("model-primitives")) { + model.customRaw = root["model-primitives"]; + } else if (model.name.empty()) { + throw std::runtime_error( + name + ": no 'model-primitives' or 'model-name' found" + ); + } + } + } else if (!modelTypeName.empty()) { + logger.error() << "unknown model: " << modelTypeName; + model.type = BlockModelType::NONE; + } + std::string cullingModeName = CullingModeMeta.getNameString(variant.culling); + root.at("culling").get(cullingModeName); + if (!CullingModeMeta.getItem(cullingModeName, variant.culling)) { + logger.error() << "unknown culling mode: " << cullingModeName; + } + root.at("draw-group").get(variant.drawGroup); +} + template<> void ContentUnitLoader::loadUnit( Block& def, const std::string& name, const io::path& file ) { @@ -72,44 +115,7 @@ template<> void ContentUnitLoader::loadUnit( root.at("caption").get(def.caption); - // block texturing - if (root.has("texture")) { - const auto& texture = root["texture"].asString(); - for (uint i = 0; i < 6; i++) { - def.textureFaces[i] = texture; - } - } else if (root.has("texture-faces")) { - const auto& texarr = root["texture-faces"]; - for (uint i = 0; i < 6; i++) { - def.textureFaces[i] = texarr[i].asString(); - } - } - - // block model - auto& model = def.model; - std::string modelTypeName = BlockModelTypeMeta.getNameString(model.type); - root.at("model").get(modelTypeName); - root.at("model-name").get(def.model.name); - if (BlockModelTypeMeta.getItem(modelTypeName, model.type)) { - if (model.type == BlockModelType::CUSTOM && def.model.customRaw == nullptr) { - if (root.has("model-primitives")) { - def.model.customRaw = root["model-primitives"]; - } else if (def.model.name.empty()) { - throw std::runtime_error( - name + ": no 'model-primitives' or 'model-name' found" - ); - } - } - } else if (!modelTypeName.empty()) { - logger.error() << "unknown model: " << modelTypeName; - model.type = BlockModelType::NONE; - } - - std::string cullingModeName = CullingModeMeta.getNameString(def.culling); - root.at("culling").get(cullingModeName); - if (!CullingModeMeta.getItem(cullingModeName, def.culling)) { - logger.error() << "unknown culling mode: " << cullingModeName; - } + load_variant(def.defaults, root, name); root.at("material").get(def.material); @@ -176,9 +182,10 @@ template<> void ContentUnitLoader::loadUnit( "block " + util::quote(def.name) + ": invalid block size" ); } - if (model.type == BlockModelType::BLOCK && + // FIXME + if (def.defaults.model.type == BlockModelType::BLOCK && (def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) { - model.type = BlockModelType::AABB; + def.defaults.model.type = BlockModelType::AABB; def.hitboxes = {AABB(def.size)}; } } @@ -194,7 +201,6 @@ template<> void ContentUnitLoader::loadUnit( root.at("selectable").get(def.selectable); root.at("grounded").get(def.grounded); root.at("hidden").get(def.hidden); - root.at("draw-group").get(def.drawGroup); root.at("picking-item").get(def.pickingItem); root.at("surface-replacement").get(def.surfaceReplacement); root.at("script-name").get(def.scriptName); diff --git a/src/core_defs.cpp b/src/core_defs.cpp index fdc880da..79551cd4 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -14,12 +14,12 @@ void corecontent::setup(Input& input, ContentBuilder& builder) { { Block& block = builder.blocks.create(CORE_AIR); block.replaceable = true; - block.drawGroup = 1; block.lightPassing = true; block.skyLightPassing = true; block.obstacle = false; block.selectable = false; - block.model.type = BlockModelType::NONE; + block.defaults.drawGroup = 1; + block.defaults.model.type = BlockModelType::NONE; block.pickingItem = CORE_EMPTY; } { @@ -37,7 +37,7 @@ void corecontent::setup(Input& input, ContentBuilder& builder) { { Block& block = builder.blocks.create(CORE_OBSTACLE); for (uint i = 0; i < 6; i++) { - block.textureFaces[i] = "obstacle"; + block.defaults.textureFaces[i] = "obstacle"; } block.hitboxes = {AABB()}; block.breakable = false; @@ -50,9 +50,9 @@ void corecontent::setup(Input& input, ContentBuilder& builder) { { Block& block = builder.blocks.create(CORE_STRUCT_AIR); for (uint i = 0; i < 6; i++) { - block.textureFaces[i] = "struct_air"; + block.defaults.textureFaces[i] = "struct_air"; } - block.drawGroup = -1; + block.defaults.drawGroup = -1; block.skyLightPassing = true; block.lightPassing = true; block.hitboxes = {AABB()}; diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index 412a3f63..3b898ac2 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -22,21 +22,30 @@ ContentGfxCache::ContentGfxCache( refresh(); } -void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) { +static void refresh_variant( + const Assets& assets, + const Block& def, + const Variant& variant, + uint8_t variantIndex, + std::unique_ptr& sideregions, + const Atlas& atlas, + const GraphicsSettings& settings, + std::unordered_map& models +) { for (uint side = 0; side < 6; side++) { - std::string tex = def.textureFaces[side]; - if (def.culling == CullingMode::OPTIONAL && + std::string tex = variant.textureFaces[side]; + if (variant.culling == CullingMode::OPTIONAL && !settings.denseRender.get() && atlas.has(tex + "_opaque")) { tex = tex + "_opaque"; } if (atlas.has(tex)) { - sideregions[def.rt.id * 6 + side] = atlas.get(tex); + sideregions[(def.rt.id * 6 + side) * MAX_VARIANTS + variantIndex] = atlas.get(tex); } else if (atlas.has(TEXTURE_NOTFOUND)) { - sideregions[def.rt.id * 6 + side] = atlas.get(TEXTURE_NOTFOUND); + sideregions[(def.rt.id * 6 + side) * MAX_VARIANTS + variantIndex] = atlas.get(TEXTURE_NOTFOUND); } } - if (def.model.type == BlockModelType::CUSTOM) { - auto model = assets.require(def.model.name); + if (variant.model.type == BlockModelType::CUSTOM) { + auto model = assets.require(variant.model.name); for (auto& mesh : model.meshes) { size_t pos = mesh.texture.find(':'); @@ -53,9 +62,19 @@ void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) { } } +void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) { + refresh_variant(assets, def, def.defaults, 0, sideregions, atlas, settings, models); + if (def.variants) { + const auto& variants = def.variants->variants; + for (int i = 0; i < variants.size(); i++) { + refresh_variant(assets, def, variants[i], i + 1, sideregions, atlas, settings, models); + } + } +} + void ContentGfxCache::refresh() { auto indices = content.getIndices(); - sideregions = std::make_unique(indices->blocks.count() * 6); + sideregions = std::make_unique(indices->blocks.count() * 6 * MAX_VARIANTS); const auto& atlas = assets.require("blocks"); const auto& blocks = indices->blocks.getIterable(); diff --git a/src/frontend/ContentGfxCache.hpp b/src/frontend/ContentGfxCache.hpp index b5816c82..fdeec60d 100644 --- a/src/frontend/ContentGfxCache.hpp +++ b/src/frontend/ContentGfxCache.hpp @@ -16,6 +16,8 @@ class Block; struct UVRegion; struct GraphicsSettings; +inline constexpr int MAX_VARIANTS = 16; + class ContentGfxCache { const Content& content; const Assets& assets; @@ -32,8 +34,8 @@ public: ); ~ContentGfxCache(); - inline const UVRegion& getRegion(blockid_t id, int side) const { - return sideregions[id * 6 + side]; + inline const UVRegion& getRegion(blockid_t id, uint8_t variant, int side) const { + return sideregions[(id * 6 + side) * MAX_VARIANTS + variant]; } const model::Model& getModel(blockid_t id) const; diff --git a/src/graphics/render/BlockWrapsRenderer.cpp b/src/graphics/render/BlockWrapsRenderer.cpp index 3a5f118c..86c46c98 100644 --- a/src/graphics/render/BlockWrapsRenderer.cpp +++ b/src/graphics/render/BlockWrapsRenderer.cpp @@ -44,7 +44,7 @@ void BlockWrapsRenderer::draw(const BlockWrapper& wrapper) { } if (vox->id != BLOCK_VOID) { const auto& def = level.content.getIndices()->blocks.require(vox->id); - switch (def.model.type) { + switch (def.getModel(vox->state.userbits).type) { case BlockModelType::BLOCK: batch->cube( glm::vec3(wrapper.position) + glm::vec3(0.5f), diff --git a/src/graphics/render/BlocksPreview.cpp b/src/graphics/render/BlocksPreview.cpp index 34c3e7df..77df92de 100644 --- a/src/graphics/render/BlocksPreview.cpp +++ b/src/graphics/render/BlocksPreview.cpp @@ -27,12 +27,12 @@ std::unique_ptr BlocksPreview::draw( ){ display::clear(); blockid_t id = def.rt.id; - const UVRegion texfaces[6]{cache.getRegion(id, 0), cache.getRegion(id, 1), - cache.getRegion(id, 2), cache.getRegion(id, 3), - cache.getRegion(id, 4), cache.getRegion(id, 5)}; + const UVRegion texfaces[6]{cache.getRegion(id, 0, 0), cache.getRegion(id, 0, 1), + cache.getRegion(id, 0, 2), cache.getRegion(id, 0, 3), + cache.getRegion(id, 0, 4), cache.getRegion(id, 0, 5)}; glm::vec3 offset(0.1f, 0.5f, 0.1f); - switch (def.model.type) { + switch (def.defaults.model.type) { case BlockModelType::NONE: // something went wrong... break; diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 483ed89e..ed22b427 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -292,21 +292,22 @@ static bool is_aligned(const glm::vec3& v, float e = 1e-6f) { } void BlocksRenderer::blockCustomModel( - const glm::ivec3& icoord, const Block* block, ubyte rotation, bool lights, bool ao + const glm::ivec3& icoord, const Block& block, blockstate states, bool lights, bool ao ) { + const auto& variant = block.getVariant(states.userbits); glm::vec3 X(1, 0, 0); glm::vec3 Y(0, 1, 0); glm::vec3 Z(0, 0, 1); glm::vec3 coord(icoord); - if (block->rotatable) { - auto& rotations = block->rotations; - CoordSystem orient = rotations.variants[rotation]; + if (block.rotatable) { + auto& rotations = block.rotations; + CoordSystem orient = rotations.variants[states.rotation]; X = orient.axes[0]; Y = orient.axes[1]; Z = orient.axes[2]; } - const auto& model = cache.getModel(block->rt.id); + const auto& model = cache.getModel(block.rt.id); for (const auto& mesh : model.meshes) { if (vertexCount + mesh.vertices.size() >= capacity) { overflow = true; @@ -328,7 +329,7 @@ void BlocksRenderer::blockCustomModel( 0.5f; vp = vp.x * X + vp.y * Y + vp.z * Z; - if (!isOpen(glm::floor(coord + vp + 0.5f + n * 1e-3f), *block) && is_aligned(n)) { + if (!isOpen(glm::floor(coord + vp + 0.5f + n * 1e-3f), block, variant) && is_aligned(n)) { continue; } @@ -369,6 +370,7 @@ void BlocksRenderer::blockCube( bool lights, bool ao ) { + const auto& variant = block.getVariant(states.userbits); glm::ivec3 X(1, 0, 0); glm::ivec3 Y(0, 1, 0); glm::ivec3 Z(0, 0, 1); @@ -382,41 +384,41 @@ void BlocksRenderer::blockCube( } if (ao) { - if (isOpen(coord + Z, block)) { + if (isOpen(coord + Z, block, variant)) { faceAO(coord, X, Y, Z, texfaces[5], lights); } - if (isOpen(coord - Z, block)) { + if (isOpen(coord - Z, block, variant)) { faceAO(coord, -X, Y, -Z, texfaces[4], lights); } - if (isOpen(coord + Y, block)) { + if (isOpen(coord + Y, block, variant)) { faceAO(coord, X, -Z, Y, texfaces[3], lights); } - if (isOpen(coord - Y, block)) { + if (isOpen(coord - Y, block, variant)) { faceAO(coord, X, Z, -Y, texfaces[2], lights); } - if (isOpen(coord + X, block)) { + if (isOpen(coord + X, block, variant)) { faceAO(coord, -Z, Y, X, texfaces[1], lights); } - if (isOpen(coord - X, block)) { + if (isOpen(coord - X, block, variant)) { faceAO(coord, Z, Y, -X, texfaces[0], lights); } } else { - if (isOpen(coord + Z, block)) { + if (isOpen(coord + Z, block, variant)) { face(coord, X, Y, Z, texfaces[5], pickLight(coord + Z), lights); } - if (isOpen(coord - Z, block)) { + if (isOpen(coord - Z, block, variant)) { face(coord, -X, Y, -Z, texfaces[4], pickLight(coord - Z), lights); } - if (isOpen(coord + Y, block)) { + if (isOpen(coord + Y, block, variant)) { face(coord, X, -Z, Y, texfaces[3], pickLight(coord + Y), lights); } - if (isOpen(coord - Y, block)) { + if (isOpen(coord - Y, block, variant)) { face(coord, X, Z, -Y, texfaces[2], pickLight(coord - Y), lights); } - if (isOpen(coord + X, block)) { + if (isOpen(coord + X, block, variant)) { face(coord, -Z, Y, X, texfaces[1], pickLight(coord + X), lights); } - if (isOpen(coord - X, block)) { + if (isOpen(coord - X, block, variant)) { face(coord, Z, Y, -X, texfaces[0], pickLight(coord - X), lights); } } @@ -486,21 +488,26 @@ void BlocksRenderer::render( blockid_t id = vox.id; blockstate state = vox.state; const auto& def = *blockDefsCache[id]; - if (id == 0 || def.drawGroup != drawGroup || state.segment) { + const auto& variant = def.getVariant(state.userbits); + uint8_t variantId = state.userbits; + if (id == 0 || variant.drawGroup != drawGroup || state.segment) { continue; } if (def.translucent) { continue; } const UVRegion texfaces[6] { - cache.getRegion(id, 0), cache.getRegion(id, 1), - cache.getRegion(id, 2), cache.getRegion(id, 3), - cache.getRegion(id, 4), cache.getRegion(id, 5) + cache.getRegion(id, variantId, 0), + cache.getRegion(id, variantId, 1), + cache.getRegion(id, variantId, 2), + cache.getRegion(id, variantId, 3), + cache.getRegion(id, variantId, 4), + cache.getRegion(id, variantId, 5) }; int x = i % CHUNK_W; int y = i / (CHUNK_D * CHUNK_W); int z = (i / CHUNK_D) % CHUNK_W; - switch (def.model.type) { + switch (def.getModel(state.userbits).type) { case BlockModelType::BLOCK: blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless, def.ambientOcclusion); @@ -516,8 +523,13 @@ void BlocksRenderer::render( break; } case BlockModelType::CUSTOM: { - blockCustomModel({x, y, z}, &def, vox.state.rotation, - !def.shadeless, def.ambientOcclusion); + blockCustomModel( + {x, y, z}, + def, + vox.state, + !def.shadeless, + def.ambientOcclusion + ); break; } default: @@ -549,21 +561,26 @@ SortingMeshData BlocksRenderer::renderTranslucent( blockid_t id = vox.id; blockstate state = vox.state; const auto& def = *blockDefsCache[id]; - if (id == 0 || def.drawGroup != drawGroup || state.segment) { + uint8_t variantId = state.userbits; + const auto& variant = def.getVariant(variantId); + if (id == 0 || variant.drawGroup != drawGroup || state.segment) { continue; } if (!def.translucent) { continue; } const UVRegion texfaces[6] { - cache.getRegion(id, 0), cache.getRegion(id, 1), - cache.getRegion(id, 2), cache.getRegion(id, 3), - cache.getRegion(id, 4), cache.getRegion(id, 5) + cache.getRegion(id, variantId, 0), + cache.getRegion(id, variantId, 1), + cache.getRegion(id, variantId, 2), + cache.getRegion(id, variantId, 3), + cache.getRegion(id, variantId, 4), + cache.getRegion(id, variantId, 5) }; int x = i % CHUNK_W; int y = i / (CHUNK_D * CHUNK_W); int z = (i / CHUNK_D) % CHUNK_W; - switch (def.model.type) { + switch (def.getModel(state.userbits).type) { case BlockModelType::BLOCK: blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless, def.ambientOcclusion); @@ -579,7 +596,7 @@ SortingMeshData BlocksRenderer::renderTranslucent( break; } case BlockModelType::CUSTOM: { - blockCustomModel({x, y, z}, &def, vox.state.rotation, + blockCustomModel({x, y, z}, def, vox.state, !def.shadeless, def.ambientOcclusion); break; } @@ -670,11 +687,12 @@ void BlocksRenderer::build(const Chunk* chunk, const Chunks* chunks) { const voxel& vox = voxels[i]; blockid_t id = vox.id; const auto& def = *blockDefsCache[id]; + const auto& variant = def.getVariant(vox.state.userbits); - if (beginEnds[def.drawGroup][0] == 0) { - beginEnds[def.drawGroup][0] = i+1; + if (beginEnds[variant.drawGroup][0] == 0) { + beginEnds[variant.drawGroup][0] = i+1; } - beginEnds[def.drawGroup][1] = i; + beginEnds[variant.drawGroup][1] = i; } cancelled = false; diff --git a/src/graphics/render/BlocksRenderer.hpp b/src/graphics/render/BlocksRenderer.hpp index b017a3f3..2de4afd0 100644 --- a/src/graphics/render/BlocksRenderer.hpp +++ b/src/graphics/render/BlocksRenderer.hpp @@ -113,8 +113,8 @@ class BlocksRenderer { ); void blockCustomModel( const glm::ivec3& icoord, - const Block* block, - ubyte rotation, + const Block& block, + blockstate states, bool lights, bool ao ); @@ -122,24 +122,25 @@ class BlocksRenderer { bool isOpenForLight(int x, int y, int z) const; // Does block allow to see other blocks sides (is it transparent) - inline bool isOpen(const glm::ivec3& pos, const Block& def) const { - auto id = voxelsBuffer->pickBlockId( + inline bool isOpen(const glm::ivec3& pos, const Block& def, const Variant& variant) const { + auto vox = voxelsBuffer->pickBlock( chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z ); - if (id == BLOCK_VOID) { + if (vox.id == BLOCK_VOID) { return false; } - const auto& block = *blockDefsCache[id]; - if (((block.drawGroup != def.drawGroup) && block.drawGroup) || !block.rt.solid) { + const auto& block = *blockDefsCache[vox.id]; + uint8_t otherDrawGroup = block.getVariant(vox.state.userbits).drawGroup; + if ((otherDrawGroup && (otherDrawGroup != variant.drawGroup)) || !block.rt.solid) { return true; } - if ((def.culling == CullingMode::DISABLED || - (def.culling == CullingMode::OPTIONAL && + if ((variant.culling == CullingMode::DISABLED || + (variant.culling == CullingMode::OPTIONAL && settings.graphics.denseRender.get())) && - id == def.rt.id) { + vox.id == def.rt.id) { return true; } - return !id; + return !vox.id; } glm::vec4 pickLight(int x, int y, int z) const; diff --git a/src/graphics/render/ModelsGenerator.cpp b/src/graphics/render/ModelsGenerator.cpp index f1d878f2..a5782f95 100644 --- a/src/graphics/render/ModelsGenerator.cpp +++ b/src/graphics/render/ModelsGenerator.cpp @@ -11,8 +11,8 @@ static debug::Logger logger("models-generator"); static void configure_textures( model::Model& model, - const Block& blockDef, - const Assets& assets + const Assets& assets, + const std::array& textureFaces ) { for (auto& mesh : model.meshes) { auto& texture = mesh.texture; @@ -21,7 +21,7 @@ static void configure_textures( } try { int index = std::stoi(texture.substr(1)); - texture = "blocks:"+blockDef.textureFaces.at(index); + texture = "blocks:" + textureFaces.at(index); } catch (const std::invalid_argument& err) { } catch (const std::runtime_error& err) { logger.error() << err.what(); @@ -48,35 +48,43 @@ static inline UVRegion get_region_for( return texreg.region; } -void ModelsGenerator::prepare(Content& content, Assets& assets) { - for (auto& [name, def] : content.blocks.getDefs()) { - if (def->model.type == BlockModelType::CUSTOM) { - if (def->model.name.empty()) { - assets.store( - std::make_unique( - loadCustomBlockModel( - def->model.customRaw, assets, !def->shadeless - ) - ), - name + ".model" - ); - def->model.name = def->name + ".model"; - } else { - auto srcModel = assets.get(def->model.name); - if (srcModel) { - auto model = std::make_unique(*srcModel); - for (auto& mesh : model->meshes) { - if (mesh.texture.length() && mesh.texture[0] == '$') { - int index = std::stoll(mesh.texture.substr(1)); - mesh.texture = "blocks:" + def->textureFaces[index]; - } +void ModelsGenerator::prepareModel( + Assets& assets, const Block& def, Variant& variant, uint8_t variantId +) { + BlockModel& blockModel = variant.model; + if (blockModel.type == BlockModelType::CUSTOM) { + std::string modelName = def.name + ".model" + (variantId == 0 ? "" : "$" + std::to_string(variantId)); + if (blockModel.name.empty()) { + assets.store( + std::make_unique( + loadCustomBlockModel( + blockModel.customRaw, assets, !def.shadeless + ) + ), + modelName + ); + blockModel.name = modelName; + } else { + auto srcModel = assets.get(blockModel.name); + if (srcModel) { + auto model = std::make_unique(*srcModel); + for (auto& mesh : model->meshes) { + if (mesh.texture.length() && mesh.texture[0] == '$') { + int index = std::stoll(mesh.texture.substr(1)); + mesh.texture = "blocks:" + variant.textureFaces[index]; } - def->model.name = name + ".model"; - assets.store(std::move(model), def->model.name); } + blockModel.name = modelName; + assets.store(std::move(model), blockModel.name); } } } +} + +void ModelsGenerator::prepare(Content& content, Assets& assets) { + for (auto& [name, def] : content.blocks.getDefs()) { + prepareModel(assets, *def, def->defaults, 0); + } for (auto& [name, def] : content.items.getDefs()) { assets.store( std::make_unique( @@ -151,12 +159,14 @@ model::Model ModelsGenerator::generate( if (def.iconType == ItemIconType::BLOCK) { auto model = assets.require("block"); const auto& blockDef = content.blocks.require(def.icon); - if (blockDef.model.type == BlockModelType::XSPRITE) { + const auto& variant = blockDef.defaults; + const auto& blockModel = variant.model; + if (blockModel.type == BlockModelType::XSPRITE) { return create_flat_model( - "blocks:" + blockDef.textureFaces.at(0), assets + "blocks:" + blockDef.defaults.textureFaces.at(0), assets ); - } else if (blockDef.model.type == BlockModelType::CUSTOM) { - model = assets.require(blockDef.model.name); + } else if (blockModel.type == BlockModelType::CUSTOM) { + model = assets.require(blockModel.name); for (auto& mesh : model.meshes) { mesh.scale(glm::vec3(0.2f)); } @@ -164,7 +174,7 @@ model::Model ModelsGenerator::generate( } for (auto& mesh : model.meshes) { mesh.shading = !blockDef.shadeless; - switch (blockDef.model.type) { + switch (blockModel.type) { case BlockModelType::AABB: { glm::vec3 size = blockDef.hitboxes.at(0).size(); float m = glm::max(size.x, glm::max(size.y, size.z)); @@ -176,7 +186,7 @@ model::Model ModelsGenerator::generate( } mesh.scale(glm::vec3(0.2f)); } - configure_textures(model, blockDef, assets); + configure_textures(model, assets, blockDef.defaults.textureFaces); return model; } else if (def.iconType == ItemIconType::SPRITE) { return create_flat_model(def.icon, assets); diff --git a/src/graphics/render/ModelsGenerator.hpp b/src/graphics/render/ModelsGenerator.hpp index 89578dc2..f5ba7eb5 100644 --- a/src/graphics/render/ModelsGenerator.hpp +++ b/src/graphics/render/ModelsGenerator.hpp @@ -8,6 +8,7 @@ struct ItemDef; class Assets; class Content; class Block; +struct Variant; class ModelsGenerator { public: @@ -28,4 +29,8 @@ public: static model::Model loadCustomBlockModel( const dv::value& primitives, const Assets& assets, bool lighting ); + + static void prepareModel( + Assets& assets, const Block& def, Variant& variant, uint8_t variantId + ); }; diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index 031611ee..7926dbd5 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -302,7 +302,7 @@ static int l_get_textures(lua::State* L) { if (auto def = require_block(L)) { lua::createtable(L, 6, 0); for (size_t i = 0; i < 6; i++) { - lua::pushstring(L, def->textureFaces[i]); + lua::pushstring(L, def->defaults.textureFaces[i]); // TODO: variant argument lua::rawseti(L, i + 1); } return 1; @@ -312,7 +312,8 @@ static int l_get_textures(lua::State* L) { static int l_get_model(lua::State* L) { if (auto def = require_block(L)) { - return lua::pushlstring(L, BlockModelTypeMeta.getName(def->model.type)); + // TODO: variant argument + return lua::pushlstring(L, BlockModelTypeMeta.getName(def->defaults.model.type)); } return 0; } diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index 39f8bf11..559dd3c2 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -108,34 +108,32 @@ const BlockRotProfile BlockRotProfile::STAIRS { }; Block::Block(const std::string& name) - : name(name), - caption(util::id_to_caption(name)), - textureFaces { - TEXTURE_NOTFOUND, - TEXTURE_NOTFOUND, - TEXTURE_NOTFOUND, - TEXTURE_NOTFOUND, - TEXTURE_NOTFOUND, - TEXTURE_NOTFOUND - } { + : name(name), caption(util::id_to_caption(name)) { + for (int i = 0; i < defaults.textureFaces.size(); i++) { + defaults.textureFaces[i] = TEXTURE_NOTFOUND; + } } -Block::~Block() {} +Block::~Block() = default; Block::Block(std::string name, const std::string& texture) - : name(std::move(name)), - textureFaces {texture, texture, texture, texture, texture, texture} { + : name(std::move(name)) { + for (int i = 0; i < defaults.textureFaces.size(); i++) { + defaults.textureFaces[i] = TEXTURE_NOTFOUND; + } } + void Block::cloneTo(Block& dst) { dst.caption = caption; for (int i = 0; i < 6; i++) { - dst.textureFaces[i] = textureFaces[i]; + dst.defaults = defaults; + } + if (variants) { + dst.variants = std::make_unique(*variants); } dst.material = material; std::copy(&emission[0], &emission[3], dst.emission); dst.size = size; - dst.model = model; - dst.drawGroup = drawGroup; dst.lightPassing = lightPassing; dst.skyLightPassing = skyLightPassing; dst.shadeless = shadeless; diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 5d2d797d..e18abf2f 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -10,6 +10,7 @@ #include "maths/aabb.hpp" #include "typedefs.hpp" #include "util/EnumMetadata.hpp" +#include "util/stack_vector.hpp" #include "interfaces/Serializable.hpp" struct ParticlesPreset; @@ -33,6 +34,8 @@ inline constexpr uint BLOCK_AABB_GRID = 16; inline constexpr size_t MAX_USER_BLOCK_FIELDS_SIZE = 240; +inline constexpr int BLOCK_MAX_VARIANTS = 16; + inline std::string DEFAULT_MATERIAL = "base:stone"; struct BlockFuncsSet { @@ -141,6 +144,21 @@ struct BlockMaterial : Serializable { void deserialize(const dv::value& src) override; }; +struct Variant { + /// @brief Block model + BlockModel model {}; + /// @brief Textures set applied to block sides + std::array textureFaces; // -x,x, -y,y, -z,z + /// @brief Culling mode + CullingMode culling = CullingMode::DEFAULT; + /// @brief Influences visible block sides for transparent blocks + uint8_t drawGroup = 0; +}; + +struct Variants { + util::stack_vector variants; +}; + /// @brief Block properties definition class Block { public: @@ -149,8 +167,7 @@ public: std::string caption; - /// @brief Textures set applied to block sides - std::array textureFaces; // -x,x, -y,y, -z,z + Variant defaults {}; dv::value properties = nullptr; @@ -163,15 +180,6 @@ public: glm::i8vec3 size {1, 1, 1}; - /// @brief Influences visible block sides for transparent blocks - uint8_t drawGroup = 0; - - /// @brief Block model - BlockModel model {}; - - /// @brief Culling mode - CullingMode culling = CullingMode::DEFAULT; - /// @brief Does the block passing lights into itself bool lightPassing = false; @@ -243,6 +251,8 @@ public: std::unique_ptr particles; + std::unique_ptr variants; + /// @brief Runtime indices (content indexing results) struct { /// @brief block runtime integer id @@ -276,6 +286,16 @@ public: void cloneTo(Block& dst); + constexpr const Variant& getVariant(uint8_t bits) const { + if (bits == 0 || variants == nullptr) + return defaults; + return variants->variants[(bits - 1) % variants->variants.size()]; + } + + constexpr const BlockModel& getModel(uint8_t bits) const { + return getVariant(bits).model; + } + static bool isReservedBlockField(std::string_view view); }; diff --git a/src/voxels/VoxelsVolume.hpp b/src/voxels/VoxelsVolume.hpp index 1a7e2b50..72afc078 100644 --- a/src/voxels/VoxelsVolume.hpp +++ b/src/voxels/VoxelsVolume.hpp @@ -56,6 +56,15 @@ public: return voxels[vox_index(bx - x, by - y, bz - z, w, d)].id; } + + inline voxel pickBlock(int bx, int by, int bz) const { + if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h || + bz >= z + d) { + return {BLOCK_VOID, {}}; + } + return voxels[vox_index(bx - x, by - y, bz - z, w, d)]; + } + inline light_t pickLight(int bx, int by, int bz) const { if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h || bz >= z + d) { From 325f0b1f025fe31f47473a4659f3dcbdc3d46a8f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Jul 2025 22:17:43 +0300 Subject: [PATCH 04/10] feat: variants in block json --- src/content/loading/BlockLoader.cpp | 16 ++++++++++++++++ src/frontend/ContentGfxCache.cpp | 5 ++--- src/voxels/Block.hpp | 4 ++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/content/loading/BlockLoader.cpp b/src/content/loading/BlockLoader.cpp index 1936f94e..4d13c4c0 100644 --- a/src/content/loading/BlockLoader.cpp +++ b/src/content/loading/BlockLoader.cpp @@ -117,6 +117,22 @@ template<> void ContentUnitLoader::loadUnit( load_variant(def.defaults, root, name); + if (root.has("state-based")) { + const auto& stateBased = root["state-based"]; + if (stateBased.has("variants")) { + const auto& variants = stateBased["variants"]; + def.variants = std::make_unique(); + for (int i = 0; i < variants.size(); i++) { + Variant variant = def.defaults; + load_variant(variant, variants[i], name); + def.variants->variants.push_back(variant); + } + while (def.variants->variants.size() < BLOCK_MAX_VARIANTS) { + def.variants->variants.push_back(def.defaults); + } + } + } + root.at("material").get(def.material); // rotation profile diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index 3b898ac2..85d484ba 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -66,7 +66,7 @@ void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) { refresh_variant(assets, def, def.defaults, 0, sideregions, atlas, settings, models); if (def.variants) { const auto& variants = def.variants->variants; - for (int i = 0; i < variants.size(); i++) { + for (int i = 0; i < variants.size() - 1; i++) { refresh_variant(assets, def, variants[i], i + 1, sideregions, atlas, settings, models); } } @@ -79,8 +79,7 @@ void ContentGfxCache::refresh() { const auto& blocks = indices->blocks.getIterable(); for (blockid_t i = 0; i < blocks.size(); i++) { - auto def = blocks[i]; - refresh(*def, atlas); + refresh(*blocks[i], atlas); } } diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index e18abf2f..c620ed3a 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -156,7 +156,7 @@ struct Variant { }; struct Variants { - util::stack_vector variants; + util::stack_vector variants {}; }; /// @brief Block properties definition @@ -289,7 +289,7 @@ public: constexpr const Variant& getVariant(uint8_t bits) const { if (bits == 0 || variants == nullptr) return defaults; - return variants->variants[(bits - 1) % variants->variants.size()]; + return variants->variants[(bits - 1) % BLOCK_MAX_VARIANTS]; } constexpr const BlockModel& getModel(uint8_t bits) const { From 5fdff1f8f663ac77bae3f41ed0b35f89466340a4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Jul 2025 22:56:06 +0300 Subject: [PATCH 05/10] fix block solidity property --- src/content/ContentBuilder.cpp | 14 +++++++++----- src/graphics/render/BlocksRenderer.hpp | 5 +++-- src/voxels/Block.hpp | 7 ++++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index e7b8df3b..6f4b02a2 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -28,13 +28,17 @@ std::unique_ptr ContentBuilder::build() { // Generating runtime info def.rt.id = blockDefsIndices.size(); def.rt.emissive = *reinterpret_cast(def.emission); - def.rt.solid = def.defaults.model.type == BlockModelType::BLOCK; // FIXME - def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1; - const float EPSILON = 0.01f; - if (def.rt.extended && glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size) { - def.rt.solid = true; + // TODO: refactor + def.defaults.rt.solid = def.defaults.model.type == BlockModelType::BLOCK; + if (def.variants) { + for (auto& variant : def.variants->variants) { + variant.rt.solid = variant.model.type == BlockModelType::BLOCK; + } } + const float EPSILON = 0.01f; + def.rt.solid = glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size; + def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1; if (def.rotatable) { for (uint i = 0; i < BlockRotProfile::MAX_COUNT; i++) { diff --git a/src/graphics/render/BlocksRenderer.hpp b/src/graphics/render/BlocksRenderer.hpp index 2de4afd0..54f7f44e 100644 --- a/src/graphics/render/BlocksRenderer.hpp +++ b/src/graphics/render/BlocksRenderer.hpp @@ -130,8 +130,9 @@ class BlocksRenderer { return false; } const auto& block = *blockDefsCache[vox.id]; - uint8_t otherDrawGroup = block.getVariant(vox.state.userbits).drawGroup; - if ((otherDrawGroup && (otherDrawGroup != variant.drawGroup)) || !block.rt.solid) { + const auto& blockVariant = block.getVariant(vox.state.userbits); + uint8_t otherDrawGroup = blockVariant.drawGroup; + if ((otherDrawGroup && (otherDrawGroup != variant.drawGroup)) || !blockVariant.rt.solid) { return true; } if ((variant.culling == CullingMode::DISABLED || diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index c620ed3a..37dc2156 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -153,6 +153,11 @@ struct Variant { CullingMode culling = CullingMode::DEFAULT; /// @brief Influences visible block sides for transparent blocks uint8_t drawGroup = 0; + + struct { + /// @brief is the block completely opaque for render + bool solid = true; + } rt; }; struct Variants { @@ -258,7 +263,7 @@ public: /// @brief block runtime integer id blockid_t id; - /// @brief is the block completely opaque for render and raycast + /// @brief is the block completely opaque for raycast bool solid = true; /// @brief does the block emit any lights From 29021d11787119564b08d46d574118917cc86ca5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Jul 2025 00:02:29 +0300 Subject: [PATCH 06/10] refactor a bit --- src/assets/AssetsLoader.cpp | 3 ++- src/content/ContentBuilder.cpp | 6 ++++-- src/content/loading/BlockLoader.cpp | 4 +++- src/frontend/ContentGfxCache.cpp | 3 ++- src/voxels/Block.cpp | 1 + src/voxels/Block.hpp | 3 ++- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 971e754d..8dc7c31c 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -272,11 +272,12 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { } } for (const auto& [_, def] : content->blocks.getDefs()) { - add_variant(loader, def->defaults); if (def->variants) { for (const auto& variant : def->variants->variants) { add_variant(loader, variant); } + } else { + add_variant(loader, def->defaults); } } for (const auto& [_, def] : content->items.getDefs()) { diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index 6f4b02a2..5c802030 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -29,13 +29,15 @@ std::unique_ptr ContentBuilder::build() { def.rt.id = blockDefsIndices.size(); def.rt.emissive = *reinterpret_cast(def.emission); - // TODO: refactor - def.defaults.rt.solid = def.defaults.model.type == BlockModelType::BLOCK; if (def.variants) { for (auto& variant : def.variants->variants) { variant.rt.solid = variant.model.type == BlockModelType::BLOCK; } + def.defaults = def.variants->variants.at(0); + } else { + def.defaults.rt.solid = def.defaults.model.type == BlockModelType::BLOCK; } + const float EPSILON = 0.01f; def.rt.solid = glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size; def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1; diff --git a/src/content/loading/BlockLoader.cpp b/src/content/loading/BlockLoader.cpp index 4d13c4c0..ebe1fe60 100644 --- a/src/content/loading/BlockLoader.cpp +++ b/src/content/loading/BlockLoader.cpp @@ -122,6 +122,7 @@ template<> void ContentUnitLoader::loadUnit( if (stateBased.has("variants")) { const auto& variants = stateBased["variants"]; def.variants = std::make_unique(); + def.variants->variants.push_back(def.defaults); for (int i = 0; i < variants.size(); i++) { Variant variant = def.defaults; load_variant(variant, variants[i], name); @@ -198,7 +199,8 @@ template<> void ContentUnitLoader::loadUnit( "block " + util::quote(def.name) + ": invalid block size" ); } - // FIXME + + // should variant modify hitbox? if (def.defaults.model.type == BlockModelType::BLOCK && (def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) { def.defaults.model.type = BlockModelType::AABB; diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index 85d484ba..18645259 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -66,9 +66,10 @@ void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) { refresh_variant(assets, def, def.defaults, 0, sideregions, atlas, settings, models); if (def.variants) { const auto& variants = def.variants->variants; - for (int i = 0; i < variants.size() - 1; i++) { + for (int i = 1; i < variants.size() - 1; i++) { refresh_variant(assets, def, variants[i], i + 1, sideregions, atlas, settings, models); } + def.variants->variants.at(0) = def.defaults; } } diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index 559dd3c2..4685dfb6 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -128,6 +128,7 @@ void Block::cloneTo(Block& dst) { for (int i = 0; i < 6; i++) { dst.defaults = defaults; } + dst.defaults = defaults; if (variants) { dst.variants = std::make_unique(*variants); } diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 37dc2156..f5a467c3 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -161,6 +161,7 @@ struct Variant { }; struct Variants { + /// First variant is copy of Block::defaults util::stack_vector variants {}; }; @@ -294,7 +295,7 @@ public: constexpr const Variant& getVariant(uint8_t bits) const { if (bits == 0 || variants == nullptr) return defaults; - return variants->variants[(bits - 1) % BLOCK_MAX_VARIANTS]; + return variants->variants[bits % BLOCK_MAX_VARIANTS]; } constexpr const BlockModel& getModel(uint8_t bits) const { From 7e0b58d96b4d7c121f317028174a4a8aee1c5660 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Jul 2025 00:20:49 +0300 Subject: [PATCH 07/10] add 'offset', 'bits' properties & fix is_solid_at --- src/content/ContentBuilder.cpp | 6 ++++-- src/content/loading/BlockLoader.cpp | 11 +++++++++++ src/voxels/Block.hpp | 10 +++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index 5c802030..ff0bb18a 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -37,9 +37,11 @@ std::unique_ptr ContentBuilder::build() { } else { def.defaults.rt.solid = def.defaults.model.type == BlockModelType::BLOCK; } - + const float EPSILON = 0.01f; - def.rt.solid = glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size; + def.rt.solid = + def.obstacle && + (glm::i8vec3(def.hitboxes[0].size() + EPSILON) == def.size); def.rt.extended = def.size.x > 1 || def.size.y > 1 || def.size.z > 1; if (def.rotatable) { diff --git a/src/content/loading/BlockLoader.cpp b/src/content/loading/BlockLoader.cpp index ebe1fe60..65e35a69 100644 --- a/src/content/loading/BlockLoader.cpp +++ b/src/content/loading/BlockLoader.cpp @@ -121,7 +121,18 @@ template<> void ContentUnitLoader::loadUnit( const auto& stateBased = root["state-based"]; if (stateBased.has("variants")) { const auto& variants = stateBased["variants"]; + + int offset = 0; + int bitsCount = 4; + stateBased.at("offset").get(offset); + stateBased.at("bits").get(bitsCount); + if (offset < 0 || bitsCount <= 0 || offset + bitsCount > 8) { + throw std::runtime_error("Invalid state-based bits configuration"); + } + def.variants = std::make_unique(); + def.variants->offset = 0; + def.variants->mask = 0xF; def.variants->variants.push_back(def.defaults); for (int i = 0; i < variants.size(); i++) { Variant variant = def.defaults; diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index f5a467c3..2ffe99e2 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -161,6 +161,8 @@ struct Variant { }; struct Variants { + uint8_t offset; + uint8_t mask; /// First variant is copy of Block::defaults util::stack_vector variants {}; }; @@ -292,10 +294,12 @@ public: void cloneTo(Block& dst); - constexpr const Variant& getVariant(uint8_t bits) const { - if (bits == 0 || variants == nullptr) + constexpr const Variant& getVariant(uint8_t userbits) const { + if (userbits == 0 || variants == nullptr) return defaults; - return variants->variants[bits % BLOCK_MAX_VARIANTS]; + return variants->variants[ + (userbits >> variants->offset) & variants->mask + ]; } constexpr const BlockModel& getModel(uint8_t bits) const { From 7573c6c7d1c555a836211a4bbd031b426b369197 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Jul 2025 01:06:38 +0300 Subject: [PATCH 08/10] add block.get_variant, block.set_variant --- src/logic/scripting/lua/libs/libblock.cpp | 60 ++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index 7926dbd5..bb527c9b 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -245,8 +245,25 @@ static int l_get_user_bits(lua::State* L) { } } uint mask = ((1 << bits) - 1) << offset; - uint data = (blockstate2int(vox->state) & mask) >> offset; - return lua::pushinteger(L, data); + return lua::pushinteger(L, (blockstate2int(vox->state) & mask) >> offset); +} + +static int l_get_variant(lua::State* L) { + auto x = lua::tointeger(L, 1); + auto y = lua::tointeger(L, 2); + auto z = lua::tointeger(L, 3); + + auto vox = blocks_agent::get(*level->chunks, x, y, z); + if (vox == nullptr) { + return lua::pushinteger(L, 0); + } + const auto& def = content->getIndices()->blocks.require(vox->id); + if (def.variants == nullptr) { + return lua::pushinteger(L, 0); + } + return lua::pushinteger( + L, (vox->state.userbits >> def.variants->offset) & def.variants->mask + ); } static int l_set_user_bits(lua::State* L) { @@ -282,6 +299,43 @@ static int l_set_user_bits(lua::State* L) { return 0; } +static int l_set_variant(lua::State* L) { + auto& chunks = *level->chunks; + auto x = lua::tointeger(L, 1); + auto y = lua::tointeger(L, 2); + auto z = lua::tointeger(L, 3); + + int cx = floordiv(x); + int cz = floordiv(z); + auto chunk = blocks_agent::get_chunk(chunks, cx, cz); + if (chunk == nullptr || y < 0 || y >= CHUNK_H) { + return 0; + } + int lx = x - cx * CHUNK_W; + int lz = z - cz * CHUNK_D; + auto vox = &chunk->voxels[vox_index(lx, y, lz)]; + const auto& def = content->getIndices()->blocks.require(vox->id); + + if (def.variants == nullptr) { + return 0; + } + + auto offset = def.variants->offset; + auto mask = def.variants->mask; + auto value = (lua::tointeger(L, 4) << offset) & mask; + + if (def.rt.extended) { + auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state); + vox = blocks_agent::get(chunks, origin.x, origin.y, origin.z); + if (vox == nullptr) { + return 0; + } + } + vox->state.userbits = (vox->state.userbits & (~mask)) | value; + chunk->setModifiedAndUnsaved(); + return 0; +} + static int l_is_replaceable_at(lua::State* L) { auto x = lua::tointeger(L, 1); auto y = lua::tointeger(L, 2); @@ -653,6 +707,8 @@ const luaL_Reg blocklib[] = { {"set_rotation", lua::wrap}, {"get_user_bits", lua::wrap}, {"set_user_bits", lua::wrap}, + {"get_variant", lua::wrap}, + {"set_variant", lua::wrap}, {"is_extended", lua::wrap}, {"get_size", lua::wrap}, {"is_segment", lua::wrap}, From cadd08b132c18b13747f74f7a7a907db0c02071c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Jul 2025 01:10:10 +0300 Subject: [PATCH 09/10] update doc/*/block-properties.md --- doc/en/block-properties.md | 34 ++++++++++++++++++++++++++++++++++ doc/ru/block-properties.md | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index 055a5901..676dfa44 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -34,6 +34,7 @@ Block model type from list: - "none" - invisible block (air) - "X" - grass model (two crossed sprites) - "aabb" - model based of block hitbox (complex hitbox will be combined into one). Examples: pipes, bulbs, panels. +- "custom" - used when specifying custom models via *model-name* ### *model-name* @@ -64,6 +65,39 @@ Rotation profile (set of available block rotations and behaviour of placing bloc - "pane" - panels, doors, signs - "stairs" - "pane" + flipped variants +## Variants + +Some properties can vary dynamically, depending on the variant number stored in the user bits of the block. + +Variability parameters are specified in the `state-based` block: + +```json +{ + ... + "state-based": { + "offset": 0, + "bits": 4, + "variants": [ + {...}, + ... + ] + } +} +``` + +- `offset` specifies the bit offset from which the variant index starts. (Default is 0) +- `bits` specifies the number of bits encoding the variant index. (Default is 4). +Currently, the maximum number of variants is 16, so specifying more than 4 bits does not make practical sense. + +Properties available for variance: +- model +- model-name +- texture +- texture-faces +- model-primitives (deprecated) + +Variants are managed via `block.set_variant(x, y, z, index)`. + ## Lighting ### *emission* diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 8013ac14..1fabcd5c 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -34,6 +34,7 @@ - "none" - невидимый блок (пример: воздух) - "X" - модель травы (крест из двух спрайтов) - "aabb" - модель, соответствующая хитбоксу блока (составной хитбокс будет объединен в один). Примеры: трубы, лампочки, панели. +- "custom" - используется при указании собственной модели через *model-name* ### Имя модели - *model-name* @@ -72,6 +73,39 @@ При приближении к блоку движок создаст эмиттер, который будет работать до разрушения блока или отдаления камеры на некоторое расстояние. +## Варианты + +Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока. + +Параметры вариативности указываются в блоке `state-based`: + +```json +{ + ... + "state-based": { + "offset": 0, + "bits": 4, + "variants": [ + {...}, + ... + ] + } +} +``` + +- `offset` определяет битового смещения, с которого начинается индекс варианта. (По-умолчанию - 0) +- `bits` определяет число бит, кодирующих индекс варианта. (По-умолчанию - 4). +На данный момент максимальное число вариантов - 16, поэтому указание более 4 бит не имеет практического смысла. + +Доступные для вариативности свойства: +- model +- model-name +- texture +- texture-faces +- model-primitives (устарело) + +Управление состоянием производится через `block.set_variant(x, y, z, index)`. + ## Освещение ### Излучение - *emission*: From 9c9d34810fbea16ccc603803f5df331a216c84f2 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Jul 2025 01:13:57 +0300 Subject: [PATCH 10/10] update doc/*/scripting/builtins/libblock.md --- doc/en/scripting/builtins/libblock.md | 9 +++++++++ doc/ru/scripting/builtins/libblock.md | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/doc/en/scripting/builtins/libblock.md b/doc/en/scripting/builtins/libblock.md index 5e461313..6d657899 100644 --- a/doc/en/scripting/builtins/libblock.md +++ b/doc/en/scripting/builtins/libblock.md @@ -59,6 +59,15 @@ block.is_replaceable_at(x: int, y: int, z: int) -> bool -- Returns count of available block IDs. block.defs_count() -> int + +-- Returns the index of the item specified in the *picking-item* property. +block.get_picking_item(id: int) -> int + +-- Returns the block variant index +block.get_variant(x: int, y: int, z: int) -> int + +-- Sets the block variant by index +block.set_variant(x: int, y: int, z: int, index: int) -> int ``` ## Rotation diff --git a/doc/ru/scripting/builtins/libblock.md b/doc/ru/scripting/builtins/libblock.md index 06f35093..e5bdbb44 100644 --- a/doc/ru/scripting/builtins/libblock.md +++ b/doc/ru/scripting/builtins/libblock.md @@ -61,6 +61,12 @@ block.defs_count() -> int -- Возвращает числовой id предмета, указанного в свойстве *picking-item*. block.get_picking_item(id: int) -> int + +-- Возвращает индекс варианта блока +block.get_variant(x: int, y: int, z: int) -> int + +-- Устанавливает вариант блока по индексу +block.set_variant(x: int, y: int, z: int, index: int) -> int ``` ### Raycast