voxelcore/src/coders/vcm.cpp

245 lines
7.2 KiB
C++

#include "vcm.hpp"
#include <algorithm>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "xml.hpp"
#include "util/stringutil.hpp"
#include "graphics/commons/Model.hpp"
#include "io/io.hpp"
using namespace vcm;
using namespace xml;
static const std::unordered_map<std::string, int> side_indices {
{"north", 0},
{"south", 1},
{"top", 2},
{"bottom", 3},
{"west", 4},
{"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;
UVRegion region {};
if (root.has("region")) {
region.set(root.attr("region").asVec4());
} else {
region.scale(glm::length(right), glm::length(up));
}
if (root.has("region-scale")) {
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);
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, shading);
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_triangle(const xmlelement& root, model::Model& model) {
auto pointA = root.attr("a").asVec3();
auto pointB = root.attr("b").asVec3();
auto pointC = root.attr("c").asVec3();
glm::vec2 uvs[3] {{0, 0}, {1, 0}, {1, 1}};
bool shading = true;
if (root.has("shading")) {
shading = to_boolean(root.attr("shading"));
}
glm::vec3 ba = pointB - pointA;
glm::vec3 ca = pointC - pointA;
glm::vec3 normal = glm::normalize(glm::cross(ba, ca));
if (root.has("uv")) {
root.attr("uv").asNumbers(glm::value_ptr(uvs[0]), 6);
} else {
UVRegion region {};
if (root.has("region")) {
region.set(root.attr("region").asVec4());
}
if (root.has("region-scale")) {
region.scale(root.attr("region-scale").asVec2());
}
for (auto& uv : uvs) {
uv = region.apply(uv);
}
}
std::string texture = root.attr("texture", "$0").getText();
auto& mesh = model.addMesh(texture, shading);
mesh.addTriangle(pointA, pointB, pointC, normal, uvs[0], uvs[1], uvs[2]);
}
static void perform_box(const xmlelement& root, model::Model& model) {
auto from = root.attr("from").asVec3();
auto to = root.attr("to").asVec3();
glm::vec3 origin = (from + to) * 0.5f;
if (root.has("origin")) {
origin = root.attr("origin").asVec3();
}
glm::mat4 tsf(1.0f);
from -= origin;
to -= origin;
tsf = glm::translate(tsf, origin);
if (root.has("rotate")) {
auto text = root.attr("rotate").getText();
if (std::count(text.begin(), text.end(), ',') == 3) {
auto quat = root.attr("rotate").asVec4();
tsf *= glm::mat4_cast(glm::quat(quat.w, quat.x, quat.y, quat.z));
} else {
auto rot = root.attr("rotate").asVec3();
tsf = glm::rotate(tsf, glm::radians(rot.x), glm::vec3(1, 0, 0));
tsf = glm::rotate(tsf, glm::radians(rot.y), glm::vec3(0, 1, 0));
tsf = glm::rotate(tsf, glm::radians(rot.z), glm::vec3(0, 0, 1));
}
}
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;
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
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();
}
if (elem->has("region")) {
regions[idx].set(elem->attr("region").asVec4());
}
if (elem->has("region-scale")) {
regions[idx].scale(elem->attr("region-scale").asVec2());
}
}
}
}
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], shading);
mesh.addBox(center, halfsize, regions, enabled, tsf);
}
}
static std::unique_ptr<model::Model> 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);
} else if (tag == "tri") {
perform_triangle(*elem, model);
}
}
return std::make_unique<model::Model>(std::move(model));
}
std::unique_ptr<model::Model> vcm::parse(
std::string_view file, std::string_view src, bool usexml
) {
try {
auto doc =
usexml ? 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() + "'"
);
}
return load_model(root);
} catch (const parsing_error& err) {
throw std::runtime_error(err.errorLog());
}
}