Merge branch 'main' into volume-renderer

This commit is contained in:
MihailRis 2026-01-18 22:53:07 +03:00
commit b2df0a7e91
28 changed files with 642 additions and 184 deletions

View file

@ -1,17 +1,58 @@
#include "vcm.hpp"
#include "xml.hpp"
#include "util/stringutil.hpp"
#include "io/io.hpp"
#include <vector>
#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;
using namespace model;
using namespace rigging;
static int calc_offsets(
const Bone& bone, std::vector<glm::vec3>& dst, int index, int depth, int parent
) {
if (depth == 0) {
dst[0] = bone.getOffset();
} else {
dst[index] = dst[parent] + bone.getOffset();
}
const auto& subBones = bone.getBones();
int subIndex = index + 1;
for (int i = 0; i < subBones.size(); i++) {
subIndex += calc_offsets(*subBones[i], dst, subIndex, depth + 1, index);
}
return subIndex - index;
}
model::Model& VcmModel::squash() {
std::vector<glm::vec3> fullOffsets(skeleton->getBones().size());
calc_offsets(*skeleton->getRoot(), fullOffsets, 0, 0, 0);
Model squashed;
for (auto& [name, model] : parts) {
if (auto bone = skeleton->find(name)) {
model.translate(fullOffsets[bone->getIndex()]);
} else {
throw std::runtime_error("invalid state: bones/parts mismatch");
}
squashed.merge(std::move(model));
}
parts = { {"", std::move(squashed)} };
skeleton.reset();
return parts.at("");
}
model::Model VcmModel::squashed() const {
return std::move(VcmModel {parts, std::nullopt}).squash();
}
static const std::unordered_map<std::string, int> side_indices {
{"north", 0},
@ -26,7 +67,97 @@ static bool to_boolean(const xml::Attribute& attr) {
return attr.getText() != "off";
}
static void perform_rect(const xmlelement& root, model::Model& model) {
class ModelBuilder {
public:
ModelBuilder(Model& model) : model(model) {}
void push(const glm::mat4& matrix) {
matrices.push_back(matrix);
calculateMatrix();
}
void pop() {
matrices.pop_back();
calculateMatrix();
}
const glm::mat4& getTransform() const {
return combined;
}
void addBox(
const std::string& texture,
bool shading,
const glm::vec3& pos,
const glm::vec3& size,
const UVRegion (&uvs)[6],
const bool enabledSides[6]
) {
auto& mesh = model.addMesh(texture, shading);
mesh.addBox(pos, size, uvs, enabledSides, combined);
}
void addTriangle(
const std::string& texture,
bool shading,
const glm::vec3& a,
const glm::vec3& b,
const glm::vec3& c,
const glm::vec3& norm,
const glm::vec2& uvA,
const glm::vec2& uvB,
const glm::vec2& uvC
) {
auto& mesh = model.addMesh(texture, shading);
mesh.addTriangle(
combined * glm::vec4(a, 1.0f),
combined * glm::vec4(b, 1.0f),
combined * glm::vec4(c, 1.0f),
norm,
uvA,
uvB,
uvC
);
}
void addRect(
const std::string& texture,
bool shading,
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up,
const glm::vec3& norm,
const UVRegion& uv
) {
auto& mesh = model.addMesh(texture, shading);
mesh.addRect(pos, right, up, norm, uv, combined);
}
Model& getModel() {
return model;
}
private:
void calculateMatrix() {
combined = glm::mat4(1.0f);
for (const auto& matrix : matrices) {
combined *= matrix;
}
}
Model& model;
std::vector<glm::mat4> matrices;
glm::mat4 combined {1.0f};
};
struct Context {
VcmModel& vcmModel;
std::vector<std::unique_ptr<Bone>>& bones;
size_t& boneIndex;
};
static void perform_element(const xmlelement& root, ModelBuilder& builder, Context& ctx);
static void perform_rect(const xmlelement& root, ModelBuilder& builder) {
auto from = root.attr("from").asVec3();
auto right = root.attr("right").asVec3();
auto up = root.attr("up").asVec3();
@ -60,10 +191,11 @@ static void perform_rect(const xmlelement& root, model::Model& model) {
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(
builder.addRect(
texture,
shading,
from + right * 0.5f + up * 0.5f,
right * 0.5f,
up * 0.5f,
@ -72,7 +204,7 @@ static void perform_rect(const xmlelement& root, model::Model& model) {
);
}
static void perform_triangle(const xmlelement& root, model::Model& model) {
static void perform_triangle(const xmlelement& root, ModelBuilder& builder) {
auto pointA = root.attr("a").asVec3();
auto pointB = root.attr("b").asVec3();
auto pointC = root.attr("c").asVec3();
@ -104,11 +236,10 @@ static void perform_triangle(const xmlelement& root, model::Model& model) {
}
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]);
builder.addTriangle(texture, shading, pointA, pointB, pointC, normal, uvs[0], uvs[1], uvs[2]);
}
static void perform_box(const xmlelement& root, model::Model& model) {
static void perform_box(const xmlelement& root, ModelBuilder& builder) {
auto from = root.attr("from").asVec3();
auto to = root.attr("to").asVec3();
@ -186,7 +317,7 @@ static void perform_box(const xmlelement& root, model::Model& model) {
bool deleted[6] {};
if (root.has("delete")) {
// todo: replace by expression parsing
// todo: replace with expression parsing
auto names = util::split(root.attr("delete").getText(), ',');
for (auto& name : names) {
util::trim(name);
@ -197,36 +328,106 @@ static void perform_box(const xmlelement& root, model::Model& model) {
}
}
builder.push(tsf);
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);
builder.addBox(texfaces[i], shading, center, halfsize, regions, enabled);
}
builder.pop();
}
static std::unique_ptr<model::Model> load_model(const xmlelement& root) {
model::Model model;
static void perform_bone(const xmlelement& root, ModelBuilder& builder, Context& ctx) {
std::string name = root.attr("name", "").getText();
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);
glm::mat4 tsf(1.0f);
if (root.has("move")) {
tsf = glm::translate(tsf, root.attr("move").asVec3());
}
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));
}
}
if (root.has("scale")) {
tsf = glm::scale(tsf, root.attr("scale").asVec3());
}
return std::make_unique<model::Model>(std::move(model));
if (name.empty()) {
builder.push(std::move(tsf));
for (const auto& elem : root.getElements()) {
perform_element(*elem, builder, ctx);
}
builder.pop();
} else {
glm::vec3 origin = builder.getTransform() * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
size_t boneIndex = ctx.boneIndex++;
std::vector<std::unique_ptr<Bone>> bones;
Context boneContext {ctx.vcmModel, bones, ctx.boneIndex};
Model boneModel;
ModelBuilder boneModelBuilder(boneModel);
boneModelBuilder.push(std::move(tsf));
for (const auto& elem : root.getElements()) {
perform_element(*elem, boneModelBuilder, boneContext);
}
ctx.bones.push_back(std::make_unique<Bone>(
boneIndex, name, name, std::move(bones), std::move(origin)
));
ctx.vcmModel.parts[std::move(name)] = std::move(boneModel);
}
}
std::unique_ptr<model::Model> vcm::parse(
static void perform_element(const xmlelement& root, ModelBuilder& builder, Context& ctx) {
auto tag = root.getTag();
if (tag == "rect") {
perform_rect(root, builder);
} else if (tag == "box") {
perform_box(root, builder);
} else if (tag == "tri") {
perform_triangle(root, builder);
} else if (tag == "bone") {
perform_bone(root, builder, ctx);
}
}
static VcmModel load_model(const xmlelement& root) {
VcmModel vcmModel {};
Model model;
ModelBuilder builder(model);
size_t boneIndex = 1;
std::vector<std::unique_ptr<Bone>> bones;
Context ctx { vcmModel, bones, boneIndex };
for (const auto& elem : root.getElements()) {
perform_element(*elem, builder, ctx);
}
vcmModel.parts["root"] = std::move(model);
vcmModel.skeleton = SkeletonConfig(
"",
std::make_unique<Bone>(
0, "root", "root", std::move(bones), glm::vec3(0.0f)
),
boneIndex
);
return vcmModel;
}
VcmModel vcm::parse(
std::string_view file, std::string_view src, bool usexml
) {
try {

View file

@ -2,13 +2,26 @@
#include <memory>
#include <string>
#include <optional>
#include <unordered_map>
namespace model {
struct Model;
#include "graphics/commons/Model.hpp"
#include "objects/rigging.hpp"
namespace rigging {
class SkeletonConfig;
}
namespace vcm {
std::unique_ptr<model::Model> parse(
struct VcmModel {
std::unordered_map<std::string, model::Model> parts;
std::optional<rigging::SkeletonConfig> skeleton;
model::Model& squash();
model::Model squashed() const;
};
VcmModel parse(
std::string_view file, std::string_view src, bool usexml
);
}

View file

@ -396,7 +396,11 @@ public:
void parseSubElements(Node& node) {
skipWhitespace();
while (hasNext()) {
if (peek() != '@') {
char c = peek();
if (c == '}') {
break;
}
if (c != '@') {
throw error("unexpected character in element");
}
nextChar();
@ -415,14 +419,12 @@ public:
if (!hasNext()) {
break;
}
char c = peek();
c = peek();
if (c == '{') {
nextChar();
parseSubElements(*subnode);
expect('}');
skipWhitespace();
} else if (c == '}') {
break;
}
}
}