From 9a0f6b23b027c4a601b1f935e22e76605a69665c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 2 May 2025 15:12:01 +0300 Subject: [PATCH 01/26] feat: process external custom block models for variable textures --- src/graphics/commons/Model.cpp | 41 +++++++++++++++++++------ src/graphics/commons/Model.hpp | 15 ++++++++- src/graphics/render/ModelsGenerator.cpp | 38 ++++++++++++++++------- src/maths/UVRegion.hpp | 8 +++++ 4 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/graphics/commons/Model.cpp b/src/graphics/commons/Model.cpp index 12888d32..05835f2a 100644 --- a/src/graphics/commons/Model.cpp +++ b/src/graphics/commons/Model.cpp @@ -39,6 +39,22 @@ void Mesh::addPlane( vertices.push_back({pos-right+up, {uv.u1, uv.v2}, norm}); } +void Mesh::addRect( + const glm::vec3& pos, + const glm::vec3& right, + const glm::vec3& up, + const glm::vec3& norm, + const UVRegion& uv +) { + vertices.push_back({pos-right-up, {uv.u1, uv.v1}, norm}); + vertices.push_back({pos+right-up, {uv.u2, uv.v1}, norm}); + vertices.push_back({pos+right+up, {uv.u2, uv.v2}, norm}); + + vertices.push_back({pos-right-up, {uv.u1, uv.v1}, norm}); + vertices.push_back({pos+right+up, {uv.u2, uv.v2}, norm}); + vertices.push_back({pos-right+up, {uv.u1, uv.v2}, norm}); +} + void Mesh::addBox(const glm::vec3& pos, const glm::vec3& size) { addPlane(pos+Z*size, X*size, Y*size, Z); addPlane(pos-Z*size, -X*size, Y*size, -Z); @@ -51,16 +67,23 @@ void Mesh::addBox(const glm::vec3& pos, const glm::vec3& size) { } void Mesh::addBox( - const glm::vec3& pos, const glm::vec3& size, const UVRegion (&uvs)[6] + const glm::vec3& pos, + const glm::vec3& size, + const UVRegion (&uvs)[6], + const bool enabledSides[6] ) { - addPlane(pos+Z*size, X*size, Y*size, Z, uvs[0]); - addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[1]); - - addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[2]); - addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[3]); - - addPlane(pos+X*size, -Z*size, Y*size, X, uvs[4]); - addPlane(pos-X*size, Z*size, Y*size, -X, uvs[5]); + if (enabledSides[0]) // north + addPlane(pos+Z*size, X*size, Y*size, Z, uvs[0]); + if (enabledSides[1]) // south + addPlane(pos-Z*size, -X*size, Y*size, -Z, uvs[1]); + if (enabledSides[2]) // top + addPlane(pos+Y*size, X*size, -Z*size, Y, uvs[2]); + if (enabledSides[3]) // bottom + addPlane(pos-Y*size, X*size, Z*size, -Y, uvs[3]); + if (enabledSides[4]) // west + addPlane(pos+X*size, -Z*size, Y*size, X, uvs[4]); + if (enabledSides[5]) // east + addPlane(pos-X*size, Z*size, Y*size, -X, uvs[5]); } void Mesh::scale(const glm::vec3& size) { diff --git a/src/graphics/commons/Model.hpp b/src/graphics/commons/Model.hpp index da295cbb..0aed1b76 100644 --- a/src/graphics/commons/Model.hpp +++ b/src/graphics/commons/Model.hpp @@ -31,11 +31,19 @@ namespace model { const glm::vec3& norm, const UVRegion& region ); + void addRect( + const glm::vec3& pos, + const glm::vec3& right, + const glm::vec3& up, + const glm::vec3& norm, + const UVRegion& region + ); void addBox(const glm::vec3& pos, const glm::vec3& size); void addBox( const glm::vec3& pos, const glm::vec3& size, - const UVRegion (&texfaces)[6] + const UVRegion (&texfaces)[6], + const bool enabledSides[6] ); void scale(const glm::vec3& size); }; @@ -47,6 +55,11 @@ namespace model { /// @param texture texture name /// @return writeable Mesh Mesh& addMesh(const std::string& texture) { + for (auto& mesh : meshes) { + if (mesh.texture == texture) { + return mesh; + } + } meshes.push_back({texture, {}}); return meshes[meshes.size()-1]; } diff --git a/src/graphics/render/ModelsGenerator.cpp b/src/graphics/render/ModelsGenerator.cpp index acd6366f..44c77418 100644 --- a/src/graphics/render/ModelsGenerator.cpp +++ b/src/graphics/render/ModelsGenerator.cpp @@ -50,16 +50,28 @@ static inline UVRegion get_region_for( void ModelsGenerator::prepare(Content& content, Assets& assets) { for (auto& [name, def] : content.blocks.getDefs()) { - if (def->model.type == BlockModelType::CUSTOM && def->model.name.empty()) { - assets.store( - std::make_unique( - loadCustomBlockModel( - def->model.customRaw, assets, !def->shadeless - ) - ), - name + ".model" - ); - def->model.name = def->name + ".model"; + 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 model = assets.get(def->model.name); + if (model) { + 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]; + } + } + } + } } } for (auto& [name, def] : content.items.getDefs()) { @@ -91,8 +103,12 @@ model::Model ModelsGenerator::fromCustom( get_region_for(modelTextures[i * 6 + 1], assets), get_region_for(modelTextures[i * 6 + 0], assets) }; + bool enabled[6] {1,1,1,1,1,1}; mesh.addBox( - modelBoxes[i].center(), modelBoxes[i].size() * 0.5f, boxtexfaces + modelBoxes[i].center(), + modelBoxes[i].size() * 0.5f, + boxtexfaces, + enabled ); } for (size_t i = 0; i < points.size() / 4; i++) { diff --git a/src/maths/UVRegion.hpp b/src/maths/UVRegion.hpp index 019c8760..09a774b9 100644 --- a/src/maths/UVRegion.hpp +++ b/src/maths/UVRegion.hpp @@ -2,6 +2,7 @@ #include #include +#include struct UVRegion { float u1; @@ -51,4 +52,11 @@ struct UVRegion { u2 = cx + w * 0.5f * x; v2 = cy + h * 0.5f * y; } + + void set(const glm::vec4& vec) { + u1 = vec.x; + v1 = vec.y; + u2 = vec.z; + v2 = vec.w; + } }; From ec85260ec4c445ed7d53c8b351d4aa320e3fa7f7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 2 May 2025 15:27:05 +0300 Subject: [PATCH 02/26] add vcm format loader --- res/models/stairs.xml | 5 ++ src/assets/assetload_funcs.cpp | 41 +++++++--- src/coders/vcm.cpp | 143 +++++++++++++++++++++++++++++++++ src/coders/vcm.hpp | 12 +++ 4 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 res/models/stairs.xml create mode 100644 src/coders/vcm.cpp create mode 100644 src/coders/vcm.hpp diff --git a/res/models/stairs.xml b/res/models/stairs.xml new file mode 100644 index 00000000..5dbd344c --- /dev/null +++ b/res/models/stairs.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 2afae28c..af8fb3c9 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -10,6 +10,7 @@ #include "coders/imageio.hpp" #include "coders/json.hpp" #include "coders/obj.hpp" +#include "coders/vcm.hpp" #include "coders/vec3.hpp" #include "constants.hpp" #include "debug/Logger.hpp" @@ -300,7 +301,8 @@ assetload::postfunc assetload::sound( static void request_textures(AssetsLoader* loader, const model::Model& model) { for (auto& mesh : model.meshes) { - if (mesh.texture.find('$') == std::string::npos) { + if (mesh.texture.find('$') == std::string::npos && + mesh.texture.find(':') == std::string::npos) { auto filename = TEXTURES_FOLDER + "/" + mesh.texture; loader->add( AssetType::TEXTURE, filename, mesh.texture, nullptr @@ -337,17 +339,34 @@ assetload::postfunc assetload::model( }; } path = paths.find(file + ".obj"); - auto text = io::read_string(path); - try { - auto model = obj::parse(path.string(), text).release(); - return [=](Assets* assets) { - request_textures(loader, *model); - assets->store(std::unique_ptr(model), name); - }; - } catch (const parsing_error& err) { - std::cerr << err.errorLog() << std::endl; - throw; + if (io::exists(path)) { + auto text = io::read_string(path); + try { + auto model = obj::parse(path.string(), text).release(); + return [=](Assets* assets) { + request_textures(loader, *model); + assets->store(std::unique_ptr(model), name); + }; + } catch (const parsing_error& err) { + std::cerr << err.errorLog() << std::endl; + throw; + } } + path = paths.find(file + ".xml"); + if (io::exists(path)) { + auto text = io::read_string(path); + try { + auto model = vcm::parse(path.string(), text).release(); + return [=](Assets* assets) { + request_textures(loader, *model); + assets->store(std::unique_ptr(model), name); + }; + } catch (const parsing_error& err) { + std::cerr << err.errorLog() << std::endl; + throw; + } + } + throw std::runtime_error("could not to find model " + util::quote(file)); } static void read_anim_file( diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp new file mode 100644 index 00000000..44e0b10b --- /dev/null +++ b/src/coders/vcm.cpp @@ -0,0 +1,143 @@ +#include "vcm.hpp" + +#include + +#include "xml.hpp" +#include "util/stringutil.hpp" +#include "graphics/commons/Model.hpp" + +using namespace vcm; +using namespace xml; + +static const std::unordered_map side_indices { + {"east", 0}, + {"west", 1}, + {"top", 2}, + {"bottom", 3}, + {"back", 4}, + {"front", 5}, +}; + +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(); + + right *= -1; + from -= right; + + UVRegion region {}; + if (root.has("region")) { + region.set(root.attr("region").asVec4()); + } else { + region.scale(glm::length(right), glm::length(up)); + } + + auto flip = root.attr("flip", "").getText(); + if (flip == "h") { + std::swap(region.u1, region.u2); + right *= -1; + from -= right; + } else if (flip == "v") { + std::swap(region.v1, region.v2); + up *= -1; + from -= up; + } + std::string texture = root.attr("texture", "$0").getText(); + auto& mesh = model.addMesh(texture); + + auto normal = glm::cross(glm::normalize(right), glm::normalize(up)); + mesh.addRect( + from + right * 0.5f + up * 0.5f, + right * 0.5f, + up * 0.5f, + normal, + region + ); +} + +static void perform_box(const xmlelement& root, model::Model& model) { + auto from = root.attr("from").asVec3(); + auto to = root.attr("to").asVec3(); + + UVRegion regions[6] {}; + regions[0].scale(to.x - from.x, to.y - from.y); + regions[1].scale(from.x - to.x, to.y - from.y); + regions[2].scale(to.x - from.x, to.z - from.z); + regions[3].scale(from.x - to.x, to.z - from.z); + regions[4].scale(to.z - from.z, to.y - from.y); + regions[5].scale(from.z - to.z, to.y - from.y); + + auto center = (from + to) * 0.5f; + auto halfsize = (to - from) * 0.5f; + + std::string texfaces[6] {"$0","$1","$2","$3","$4","$5"}; + + for (const auto& elem : root.getElements()) { + if (elem->getTag() == "part") { + // todo: replace by expression parsing + auto tags = util::split(elem->attr("tags").getText(), ','); + for (auto& tag : tags) { + util::trim(tag); + const auto& found = side_indices.find(tag); + if (found == side_indices.end()) { + continue; + } + int idx = found->second; + if (elem->has("texture")) { + texfaces[idx] = elem->attr("texture").getText(); + } + } + } + } + + bool deleted[6] {}; + if (root.has("delete")) { + // todo: replace by expression parsing + auto names = util::split(root.attr("delete").getText(), ','); + for (auto& name : names) { + util::trim(name); + const auto& found = side_indices.find(name); + if (found != side_indices.end()) { + deleted[found->second] = true; + } + } + } + + for (int i = 0; i < 6; i++) { + if (deleted[i]) { + continue; + } + bool enabled[6] {}; + enabled[i] = true; + auto& mesh = model.addMesh(texfaces[i]); + mesh.addBox(center, halfsize, regions, enabled); + } +} + +static std::unique_ptr load_model(const xmlelement& root) { + model::Model model; + + for (const auto& elem : root.getElements()) { + auto tag = elem->getTag(); + + if (tag == "rect") { + perform_rect(*elem, model); + } else if (tag == "box") { + perform_box(*elem, model); + } + } + + return std::make_unique(std::move(model)); +} + +std::unique_ptr vcm::parse(std::string_view file, std::string_view src) { + auto doc = xml::parse(file, src); + const auto& root = *doc->getRoot(); + if (root.getTag() != "model") { + throw std::runtime_error( + "'model' tag expected as root, got '" + root.getTag() + "'" + ); + } + return load_model(root); +} diff --git a/src/coders/vcm.hpp b/src/coders/vcm.hpp new file mode 100644 index 00000000..6f239604 --- /dev/null +++ b/src/coders/vcm.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +namespace model { + struct Model; +} + +namespace vcm { + std::unique_ptr parse(std::string_view file, std::string_view src); +} From edb12a32b04cc08b66d239d280923f29d7a24d49 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 2 May 2025 15:32:29 +0300 Subject: [PATCH 03/26] fix side indices & update test model --- res/models/stairs.xml | 5 +++-- src/coders/vcm.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/res/models/stairs.xml b/res/models/stairs.xml index 5dbd344c..ad36aed7 100644 --- a/res/models/stairs.xml +++ b/res/models/stairs.xml @@ -1,5 +1,6 @@ - - + + + diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index 44e0b10b..55e36cbf 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -10,12 +10,12 @@ using namespace vcm; using namespace xml; static const std::unordered_map side_indices { - {"east", 0}, - {"west", 1}, + {"north", 0}, + {"south", 1}, {"top", 2}, {"bottom", 3}, - {"back", 4}, - {"front", 5}, + {"east", 4}, + {"west", 5}, }; static void perform_rect(const xmlelement& root, model::Model& model) { From 895855434f60f84a8e7dfc05e3ffd9f5b542d1c6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 2 May 2025 18:42:12 +0300 Subject: [PATCH 04/26] fix custom models instancing for different blocks --- src/frontend/ContentGfxCache.cpp | 20 +++++++++----------- src/graphics/render/ModelsGenerator.cpp | 7 +++++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index 31199a90..412a3f63 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -37,17 +37,15 @@ void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) { } if (def.model.type == BlockModelType::CUSTOM) { auto model = assets.require(def.model.name); - // temporary dirty fix tbh - if (def.model.name.find(':') == std::string::npos) { - for (auto& mesh : model.meshes) { - size_t pos = mesh.texture.find(':'); - if (pos == std::string::npos) { - continue; - } - if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) { - for (auto& vertex : mesh.vertices) { - vertex.uv = region->apply(vertex.uv); - } + + for (auto& mesh : model.meshes) { + size_t pos = mesh.texture.find(':'); + if (pos == std::string::npos) { + continue; + } + if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) { + for (auto& vertex : mesh.vertices) { + vertex.uv = region->apply(vertex.uv); } } } diff --git a/src/graphics/render/ModelsGenerator.cpp b/src/graphics/render/ModelsGenerator.cpp index 44c77418..ccaffa0d 100644 --- a/src/graphics/render/ModelsGenerator.cpp +++ b/src/graphics/render/ModelsGenerator.cpp @@ -62,14 +62,17 @@ void ModelsGenerator::prepare(Content& content, Assets& assets) { ); def->model.name = def->name + ".model"; } else { - auto model = assets.get(def->model.name); - if (model) { + 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]; } } + def->model.name = name + ".model"; + assets.store(std::move(model), def->model.name); } } } From eb034090ee64a65b14d3548009707201d1ffaf03 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 2 May 2025 18:42:40 +0300 Subject: [PATCH 05/26] fix stairs model direction --- res/models/stairs.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/models/stairs.xml b/res/models/stairs.xml index ad36aed7..9eeac7a1 100644 --- a/res/models/stairs.xml +++ b/res/models/stairs.xml @@ -1,6 +1,6 @@ - - - - + + + + From 27b194816b73b1bd97bbc1aa12b1ed16ed7a2b6f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 3 May 2025 22:59:24 +0300 Subject: [PATCH 06/26] add 'region', 'region-scale' properties --- src/coders/vcm.cpp | 13 ++++++++++++- src/maths/UVRegion.hpp | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index 55e36cbf..9e87f5ab 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -32,6 +32,9 @@ static void perform_rect(const xmlelement& root, model::Model& model) { } else { region.scale(glm::length(right), glm::length(up)); } + if (root.has("region-scale")) { + region.scale(root.attr("region-scale").asVec2()); + } auto flip = root.attr("flip", "").getText(); if (flip == "h") { @@ -87,6 +90,12 @@ static void perform_box(const xmlelement& root, model::Model& model) { if (elem->has("texture")) { texfaces[idx] = elem->attr("texture").getText(); } + if (elem->has("region")) { + regions[idx].set(elem->attr("region").asVec4()); + } + if (elem->has("region-scale")) { + regions[idx].scale(elem->attr("region-scale").asVec2()); + } } } } @@ -131,7 +140,9 @@ static std::unique_ptr load_model(const xmlelement& root) { return std::make_unique(std::move(model)); } -std::unique_ptr vcm::parse(std::string_view file, std::string_view src) { +std::unique_ptr vcm::parse( + std::string_view file, std::string_view src +) { auto doc = xml::parse(file, src); const auto& root = *doc->getRoot(); if (root.getTag() != "model") { diff --git a/src/maths/UVRegion.hpp b/src/maths/UVRegion.hpp index 09a774b9..0940cd3c 100644 --- a/src/maths/UVRegion.hpp +++ b/src/maths/UVRegion.hpp @@ -53,6 +53,10 @@ struct UVRegion { v2 = cy + h * 0.5f * y; } + void scale(const glm::vec2& vec) { + scale(vec.x, vec.y); + } + void set(const glm::vec4& vec) { u1 = vec.x; v1 = vec.y; From 4df6150692456f816af82ee6faf414bbdddc9cab Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 14 May 2025 07:01:33 +0300 Subject: [PATCH 07/26] make code editor filter pattern-safe & update script_file template --- res/layouts/code_editor.xml.lua | 14 ++++++++------ res/layouts/templates/script_file.xml | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/res/layouts/code_editor.xml.lua b/res/layouts/code_editor.xml.lua index 6cb9ce68..73f3ca46 100644 --- a/res/layouts/code_editor.xml.lua +++ b/res/layouts/code_editor.xml.lua @@ -81,13 +81,14 @@ local function refresh_file_title() end function filter_files(text) + local pattern_safe = text:pattern_safe(); local filtered = {} for _, filename in ipairs(filenames) do - if filename:find(text) then + if filename:find(pattern_safe) then table.insert(filtered, filename) end end - build_files_list(filtered, text) + build_files_list(filtered, pattern_safe) end function on_control_combination(keycode) @@ -229,15 +230,15 @@ function open_file_in_editor(filename, line, mutable) document.saveIcon.enabled = current_file.modified end -function build_files_list(filenames, selected) +function build_files_list(filenames, highlighted_part) local files_list = document.filesList files_list.scroll = 0 files_list:clear() for _, actual_filename in ipairs(filenames) do local filename = actual_filename - if selected then - filename = filename:gsub(selected, "**"..selected.."**") + if highlighted_part then + filename = filename:gsub(highlighted_part, "**"..highlighted_part.."**") end local parent = file.parent(filename) local info = registry.get_info(actual_filename) @@ -250,7 +251,8 @@ function build_files_list(filenames, selected) name = file.name(filename), icon = icon, unit = info and info.unit or '', - filename = actual_filename + filename = actual_filename, + open_func = "open_file_in_editor", })) end end diff --git a/res/layouts/templates/script_file.xml b/res/layouts/templates/script_file.xml index 94e1afd2..f5d55729 100644 --- a/res/layouts/templates/script_file.xml +++ b/res/layouts/templates/script_file.xml @@ -3,7 +3,7 @@