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);
+}