From ec85260ec4c445ed7d53c8b351d4aa320e3fa7f7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 2 May 2025 15:27:05 +0300 Subject: [PATCH] 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); +}