diff --git a/res/devtools/syntax/vcm.toml b/res/devtools/syntax/vcm.toml
new file mode 100644
index 00000000..ca05b6a1
--- /dev/null
+++ b/res/devtools/syntax/vcm.toml
@@ -0,0 +1,3 @@
+language = "VCM"
+extensions = ["vcm"]
+line-comment-start = "#"
diff --git a/res/models/stairs.vcm b/res/models/stairs.vcm
new file mode 100644
index 00000000..2027c09e
--- /dev/null
+++ b/res/models/stairs.vcm
@@ -0,0 +1,4 @@
+@box from (0,0,0) to (1,0.5,1) delete (top,south)
+@box from (0,0.5,0) to (1,1,0.5) delete (bottom,south)
+@rect from (0,0.5,0.5) right (1,0,0) up (0,0,0.5) texture "$2"
+@rect from (0,0,0) right (1,0,0) up (0,1,0) texture "$1"
diff --git a/res/models/stairs.xml b/res/models/stairs.xml
deleted file mode 100644
index 5921e08c..00000000
--- a/res/models/stairs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/res/modules/internal/scripts_registry.lua b/res/modules/internal/scripts_registry.lua
index db6fc321..ef347159 100644
--- a/res/modules/internal/scripts_registry.lua
+++ b/res/modules/internal/scripts_registry.lua
@@ -55,7 +55,7 @@ local function load_models_list(packs)
local registry = export.registry
for _, filename in ipairs(file.list("models")) do
local ext = file.ext(filename)
- if ext == "xml" then
+ if ext == "xml" or ext == "vcm" then
registry[filename] = {type="model", unit=file.stem(filename)}
table.insert(export.filenames, filename)
end
diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp
index af8fb3c9..d5994469 100644
--- a/src/assets/assetload_funcs.cpp
+++ b/src/assets/assetload_funcs.cpp
@@ -352,21 +352,35 @@ assetload::postfunc assetload::model(
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;
+
+ std::array extensions {
+ ".xml",
+ ".vcm"
+ };
+
+ path = "";
+ for (const auto& ext : extensions) {
+ auto newPath = paths.find(file + ext);
+ if (io::exists(newPath)) {
+ path = std::move(newPath);
+ break;
}
}
- throw std::runtime_error("could not to find model " + util::quote(file));
+ if (path.empty()) {
+ throw std::runtime_error("could not to find model " + util::quote(file));
+ }
+
+ 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;
+ }
}
static void read_anim_file(
diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp
index e86fc138..20c50982 100644
--- a/src/coders/vcm.cpp
+++ b/src/coders/vcm.cpp
@@ -5,6 +5,7 @@
#include "xml.hpp"
#include "util/stringutil.hpp"
#include "graphics/commons/Model.hpp"
+#include "io/io.hpp"
using namespace vcm;
using namespace xml;
@@ -143,12 +144,18 @@ static std::unique_ptr load_model(const xmlelement& root) {
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() + "'"
- );
+ try {
+ auto doc = io::path(std::string(file)).extension() == ".xml"
+ ? xml::parse(file, src) : xml::parse_vcm(file, src, "model");
+ const auto& root = *doc->getRoot();
+ if (root.getTag() != "model") {
+ throw std::runtime_error(
+ "'model' tag expected as root, got '" + root.getTag() + "'"
+ );
+ }
+ std::cout << xml::stringify(*doc) << std::endl;
+ return load_model(root);
+ } catch (const parsing_error& err) {
+ throw std::runtime_error(err.errorLog());
}
- return load_model(root);
}
diff --git a/src/coders/xml.cpp b/src/coders/xml.cpp
index 9ed51e0c..0763fa43 100644
--- a/src/coders/xml.cpp
+++ b/src/coders/xml.cpp
@@ -355,6 +355,83 @@ std::unique_ptr xml::parse(
return parser.parse();
}
+namespace {
+class VcmParser : public BasicParser {
+public:
+ VcmParser(std::string_view filename, std::string_view source)
+ : BasicParser(filename, source) {
+ document = std::make_unique("1.0", "UTF-8");
+ hashComment = true;
+ }
+
+ std::string parseValue() {
+ char c = peek();
+ if (c == '"' || c == '\'') {
+ nextChar();
+ return parseString(c);
+ }
+ if (c == '(') {
+ nextChar();
+ // TODO: replace with array parsing after moving to dv::value's
+ std::string value = std::string(readUntil(')'));
+ expect(')');
+ return value;
+ }
+ return std::string(readUntilWhitespace());
+ }
+
+ void parseSubElements(Node& node) {
+ skipWhitespace();
+ while (hasNext()) {
+ if (peek() != '@') {
+ throw error("unexpected character in element");
+ }
+ nextChar();
+ auto subnodePtr = std::make_unique(parseName());
+ auto subnode = subnodePtr.get();
+ node.add(std::move(subnodePtr));
+
+ skipWhitespace();
+ while (hasNext() && peek() != '@' && peek() != '{' && peek() != '}') {
+ std::string attrname = parseName();
+ skipWhitespace();
+ std::string value = parseValue();
+ subnode->set(attrname, value);
+ skipWhitespace();
+ }
+ if (!hasNext()) {
+ break;
+ }
+ char c = peek();
+ if (c == '{') {
+ nextChar();
+ parseSubElements(*subnode);
+ expect('}');
+ skipWhitespace();
+ } else if (c == '}') {
+ break;
+ }
+ }
+ }
+
+ std::unique_ptr parse(const std::string& rootTag) {
+ auto root = std::make_unique(rootTag);
+ parseSubElements(*root);
+ document->setRoot(std::move(root));
+ return std::move(document);
+ }
+private:
+ std::unique_ptr document;
+};
+}
+
+std::unique_ptr xml::parse_vcm(
+ std::string_view filename, std::string_view source, std::string_view tag
+) {
+ VcmParser parser(filename, source);
+ return parser.parse(std::string(tag));
+}
+
inline void newline(
std::stringstream& ss, bool nice, const std::string& indentStr, int indent
) {
diff --git a/src/coders/xml.hpp b/src/coders/xml.hpp
index f626dd34..24cf0fbd 100644
--- a/src/coders/xml.hpp
+++ b/src/coders/xml.hpp
@@ -126,5 +126,9 @@ namespace xml {
std::string_view filename, std::string_view source
);
+ std::unique_ptr parse_vcm(
+ std::string_view filename, std::string_view source, std::string_view tag
+ );
+
using xmlelement = Node;
}
diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp
index 1e0d6f20..8cfb8ca8 100644
--- a/src/logic/scripting/lua/libs/libassets.cpp
+++ b/src/logic/scripting/lua/libs/libassets.cpp
@@ -51,7 +51,7 @@ static int l_parse_model(lua::State* L) {
auto string = lua::require_lstring(L, 2);
auto name = lua::require_string(L, 3);
- if (format == "xml") {
+ if (format == "xml" || format == "vcm") {
engine->getAssets()->store(vcm::parse(name, string), name);
} else {
throw std::runtime_error("unknown format " + util::quote(std::string(format)));
diff --git a/test/coders/xml.cpp b/test/coders/xml.cpp
new file mode 100644
index 00000000..1357a449
--- /dev/null
+++ b/test/coders/xml.cpp
@@ -0,0 +1,24 @@
+#include
+
+#include "coders/xml.hpp"
+#include "coders/commons.hpp"
+
+TEST(XML, VCM) {
+ std::string code = ""
+ "@line x1 y1 z1\n"
+ "# @tst ysfdrg\n"
+ "@box {\n"
+ " @rect texture \"$2\"\n"
+ " @utro a 53.1\n"
+ "}\n"
+ ;
+ std::cout << code << std::endl;
+
+ try {
+ auto document = xml::parse_vcm("", code, "test");
+ std::cout << xml::stringify(*document);
+ } catch (const parsing_error& err) {
+ std::cerr << err.errorLog() << std::endl;
+ throw err;
+ }
+}