diff --git a/doc/ru/vcm.md b/doc/ru/vcm.md index 8943a19a0..a1b72ca48 100644 --- a/doc/ru/vcm.md +++ b/doc/ru/vcm.md @@ -1,4 +1,4 @@ -## VCM +# VCM Текстовый формат описания 3D моделей, являющийся заменой устаревшего свойства `model-primitives`. @@ -27,6 +27,8 @@ > При размерах более 1.0 результат не определён и может измениться в последующих обновлениях. > Используйте `region` или `region-scale` для ручной настройки. +## Примитивы + ### Свойства `tri` Примитив описывает треугольник. @@ -75,3 +77,25 @@ - `texture` - отображаемая текстура. По-умолчанию: `$индекс_стороны`. - `region` - UV регион в границах выбранной текстуры, определяется позициями противоположных углов. Пример: `region (0,0,1,1)` - `region-scale` вектор-множитель для автоматически выбранного UV региона. Пример: `region-scale (0.5,1)` + +## Скелет + +VCM позволяет описывать [скелет](rigging.md) модели. Для этого используется элемент `@bone` (кость). + +Существует два вида костей: +- безымянные - используются только для трансформаций на влюженные примитивы, не попадает в итоговый скелет. +- именованные - будет включено в итоговый скелет. Имя указывается через атрибут `name`. + +Главная кость (`root`) создаётся автоматически для всей модели. +При наличии явно указанных костей модель будет разделена на несколько частей именованных в формате `имя_модели.имя_кости`. +Так как модель относится к ассетам и не имеет префикса, загруженный скелет получает имя модели, так же, без префикса. +Загруженный скелет можно использовать у [сущности](entity-properties.md#имя-скелета---skeleton-name) через свойство `skeleton-name`. + +В списке `пак/preload.json` при указании параметра `"squash": true` vcm будет загружен как единая модель, без скелета. + +### Свойства `bone` + +- `name` - имя кости, при отсутствии которого кость будет "растворена" и не попадёт в итоговый скелет, при этом, трансформации будут применены. +- `move` - смещение вложенных элементов. +- `scale` - масштабирование вложенных элементов. Применяется перед смещением. +- `rotate` - вращение вложенных элементов вокруг осей (x,y,z) в градусах, или кватернион (x,y,z,w). Пример: `rotate (45,0,0)` или `rotate (0.3826834, 0, 0, 0.9238795)` diff --git a/src/assets/Assets.hpp b/src/assets/Assets.hpp index c0853b0b4..ea0753aec 100644 --- a/src/assets/Assets.hpp +++ b/src/assets/Assets.hpp @@ -24,7 +24,8 @@ enum class AssetType { LAYOUT, SOUND, MODEL, - POST_EFFECT + POST_EFFECT, + SKELETON }; namespace assetload { diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 49ce5e04d..cf64a9c8e 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -1,26 +1,26 @@ #include "AssetsLoader.hpp" -#include -#include -#include - -#include "coders/imageio.hpp" +#include "assetload_funcs.hpp" +#include "Assets.hpp" #include "coders/commons.hpp" +#include "coders/imageio.hpp" #include "constants.hpp" #include "content/Content.hpp" #include "content/ContentPack.hpp" #include "debug/Logger.hpp" +#include "engine/Engine.hpp" #include "engine/EnginePaths.hpp" -#include "io/io.hpp" #include "graphics/core/Texture.hpp" +#include "io/io.hpp" +#include "items/ItemDef.hpp" #include "logic/scripting/scripting.hpp" +#include "objects/EntityDef.hpp" #include "objects/rigging.hpp" #include "util/ThreadPool.hpp" #include "voxels/Block.hpp" -#include "items/ItemDef.hpp" -#include "Assets.hpp" -#include "assetload_funcs.hpp" -#include "engine/Engine.hpp" + +#include +#include namespace fs = std::filesystem; @@ -36,6 +36,7 @@ AssetsLoader::AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths addLoader(AssetType::SOUND, assetload::sound); addLoader(AssetType::MODEL, assetload::model); addLoader(AssetType::POST_EFFECT, assetload::posteffect); + addLoader(AssetType::SKELETON, assetload::skeleton); } void AssetsLoader::addLoader(AssetType tag, aloader_func func) { @@ -141,6 +142,8 @@ static std::string assets_def_folder(AssetType tag) { return MODELS_FOLDER; case AssetType::POST_EFFECT: return POST_EFFECTS_FOLDER; + case AssetType::SKELETON: + return SKELETONS_FOLDER; } return ""; } @@ -178,6 +181,12 @@ void AssetsLoader::processPreload( config = std::make_shared(advanced); break; } + case AssetType::MODEL: { + bool squashed = false; + map.at("squash").get(squashed); + config = std::make_shared(squashed); + break; + } default: break; } @@ -211,6 +220,7 @@ void AssetsLoader::processPreloadConfig(const io::path& file) { processPreloadList(AssetType::SOUND, root["sounds"]); processPreloadList(AssetType::MODEL, root["models"]); processPreloadList(AssetType::POST_EFFECT, root["post-effects"]); + processPreloadList(AssetType::SKELETON, root["skeletons"]); // layouts are loaded automatically } @@ -263,21 +273,20 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { add_layouts(pack->getEnvironment(), info.id, folder, loader); } - for (auto& entry : content->getSkeletons()) { - auto& skeleton = *entry.second; - for (auto& bone : skeleton.getBones()) { - std::string model = bone->model.name; - size_t pos = model.rfind('.'); - if (pos != std::string::npos) { - model = model.substr(0, pos); - } - if (!model.empty()) { - loader.add( - AssetType::MODEL, MODELS_FOLDER + "/" + model, model - ); - } + for (const auto& entry : content->getPacks()) { + io::path skeletonsDir = entry.first + ":skeletons"; + if (!io::is_directory(skeletonsDir)) { + continue; + } + for (const auto& file : io::directory_iterator(skeletonsDir)) { + loader.add( + AssetType::SKELETON, + file.string(), + entry.first + ":" + file.stem() + ); } } + for (const auto& [_, def] : content->blocks.getDefs()) { if (def->variants) { for (const auto& variant : def->variants->variants) { @@ -296,6 +305,16 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { ); } } + for (const auto& [_, def] : content->entities.getDefs()) { + if (def->skeletonName.find(':') == std::string::npos) { + // expecting a VCM with skeleton + loader.add( + AssetType::MODEL, + MODELS_FOLDER + "/" + def->skeletonName, + def->skeletonName + ); + } + } } } diff --git a/src/assets/AssetsLoader.hpp b/src/assets/AssetsLoader.hpp index e35a78ce2..78152b6ac 100644 --- a/src/assets/AssetsLoader.hpp +++ b/src/assets/AssetsLoader.hpp @@ -59,6 +59,12 @@ struct PostEffectCfg : AssetCfg { PostEffectCfg(bool advanced) : advanced(advanced) {} }; +struct ModelCfg : AssetCfg { + bool squashed; + + ModelCfg(bool squashed) : squashed(squashed) {} +}; + using aloader_func = std::function< assetload:: postfunc(AssetsLoader*, const ResPaths&, const std::string&, const std::string&, std::shared_ptr)>; diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index a1c04dfcc..4bd32d5d0 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -325,13 +325,31 @@ assetload::postfunc assetload::model( const ResPaths& paths, const std::string& file, const std::string& name, - const std::shared_ptr& + const std::shared_ptr& config ) { + auto cfg = std::dynamic_pointer_cast(config); + auto path = paths.find(file + ".vec3"); if (io::exists(path)) { auto bytes = io::read_bytes_buffer(path); auto modelVEC3 = std::make_shared(vec3::load(path.string(), bytes)); - return [loader, name, modelVEC3=std::move(modelVEC3)](Assets* assets) { + return [loader, name, cfg, modelVEC3=std::move(modelVEC3)](Assets* assets) { + if (cfg && cfg->squashed) { + model::Model fullModel; + for (auto& entry : modelVEC3->models) { + auto& vec3model = entry.second; + auto& model = vec3model.model; + model.translate(vec3model.origin); + fullModel.merge(std::move(model)); + } + request_textures(loader, fullModel); + assets->store( + std::make_unique(fullModel), + name + ); + logger.info() << "store model " << util::quote(name); + return; + } for (auto& [modelName, model] : modelVEC3->models) { request_textures(loader, model.model); std::string fullName = name; @@ -381,18 +399,76 @@ assetload::postfunc assetload::model( auto text = io::read_string(path); try { - auto model = vcm::parse(path.string(), text, path.extension() == ".xml") - .release(); - return [=](Assets* assets) { - request_textures(loader, *model); - assets->store(std::unique_ptr(model), name); - }; + auto vcmModel = vcm::parse(path.string(), text, path.extension() == ".xml"); + + assert(vcmModel.parts.size() > 0); + + if (vcmModel.parts.size() == 1 || (cfg && cfg->squashed)) { + auto modelPtr = std::make_unique(std::move(vcmModel.squash())).release(); + return [=](Assets* assets) { + auto model = std::unique_ptr(modelPtr); + request_textures(loader, *model); + assets->store(std::move(model), name); + logger.info() << "store model " << util::quote(name); + }; + } else { + auto vcmModelPtr = std::make_unique(std::move(vcmModel)).release(); + return [=](Assets* assets) { + auto vcmModel = std::unique_ptr(vcmModelPtr); + for (auto& [partName, model] : vcmModel->parts) { + auto fullName = name + "." + partName; + logger.info() + << "store model part " << util::quote(partName) + << " as " << util::quote(fullName); + assets->store( + std::make_unique(std::move(model)), + fullName + ); + } + for (auto& bone : vcmModel->skeleton->getBones()) { + bone->setModel(name + "." + bone->model.name); + } + logger.info() << "store skeleton " << util::quote(name); + assets->store( + std::make_unique( + std::move(*vcmModel->skeleton) + ), + name + ); + }; + } } catch (const parsing_error& err) { std::cerr << err.errorLog() << std::endl; throw; } } +assetload::postfunc assetload::skeleton( + AssetsLoader* loader, + const ResPaths& paths, + const std::string& file, + const std::string& name, + const std::shared_ptr& settings +) { + return [=](auto assets) { + std::string text = io::read_string(file); + auto skeleton = rigging::SkeletonConfig::parse(text, file, name); + for (auto& bone : skeleton->getBones()) { + std::string model = bone->model.name; + size_t pos = model.rfind('.'); + if (pos != std::string::npos) { + model = model.substr(0, pos); + } + if (!model.empty()) { + loader->add( + AssetType::MODEL, MODELS_FOLDER + "/" + model, model + ); + } + } + assets->store(std::move(skeleton), name); + }; +} + static void read_anim_file( const std::string& animFile, std::vector>& frameList diff --git a/src/assets/assetload_funcs.hpp b/src/assets/assetload_funcs.hpp index 97ed60ea1..50a255bb2 100644 --- a/src/assets/assetload_funcs.hpp +++ b/src/assets/assetload_funcs.hpp @@ -69,4 +69,12 @@ namespace assetload { const std::string& name, const std::shared_ptr& settings ); + + postfunc skeleton( + AssetsLoader*, + const ResPaths& paths, + const std::string& file, + const std::string& name, + const std::shared_ptr& settings + ); } diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index 5bf54ee99..0f408131d 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -1,17 +1,58 @@ #include "vcm.hpp" +#include "xml.hpp" +#include "util/stringutil.hpp" +#include "io/io.hpp" + +#include #include #include #include #include -#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& 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 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 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 matrices; + glm::mat4 combined {1.0f}; +}; + +struct Context { + VcmModel& vcmModel; + std::vector>& 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 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(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> 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( + boneIndex, name, name, std::move(bones), std::move(origin) + )); + ctx.vcmModel.parts[std::move(name)] = std::move(boneModel); + } } -std::unique_ptr 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> 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( + 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 { diff --git a/src/coders/vcm.hpp b/src/coders/vcm.hpp index e0629de18..4f97ea8d3 100644 --- a/src/coders/vcm.hpp +++ b/src/coders/vcm.hpp @@ -2,13 +2,26 @@ #include #include +#include +#include -namespace model { - struct Model; +#include "graphics/commons/Model.hpp" +#include "objects/rigging.hpp" + +namespace rigging { + class SkeletonConfig; } namespace vcm { - std::unique_ptr parse( + struct VcmModel { + std::unordered_map parts; + std::optional skeleton; + + model::Model& squash(); + model::Model squashed() const; + }; + + VcmModel parse( std::string_view file, std::string_view src, bool usexml ); } diff --git a/src/coders/xml.cpp b/src/coders/xml.cpp index 4efabc959..93e1c8031 100644 --- a/src/coders/xml.cpp +++ b/src/coders/xml.cpp @@ -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; } } } diff --git a/src/content/Content.cpp b/src/content/Content.cpp index 0c697fd1d..15d68512b 100644 --- a/src/content/Content.cpp +++ b/src/content/Content.cpp @@ -8,7 +8,6 @@ #include "items/ItemDef.hpp" #include "logic/scripting/scripting.hpp" #include "objects/EntityDef.hpp" -#include "objects/rigging.hpp" #include "voxels/Block.hpp" #include "world/generator/VoxelFragment.hpp" #include "world/generator/GeneratorDef.hpp" @@ -33,7 +32,6 @@ Content::Content( ContentUnitDefs generators, UptrsMap packs, UptrsMap blockMaterials, - UptrsMap skeletons, ResourceIndicesSet resourceIndices, dv::value defaults, std::unordered_map tags @@ -41,7 +39,6 @@ Content::Content( : indices(std::move(indices)), packs(std::move(packs)), blockMaterials(std::move(blockMaterials)), - skeletons(std::move(skeletons)), defaults(std::move(defaults)), tags(std::move(tags)), blocks(std::move(blocks)), @@ -56,23 +53,6 @@ Content::Content( Content::~Content() = default; -const rigging::SkeletonConfig* Content::getSkeleton(const std::string& id -) const { - auto found = skeletons.find(id); - if (found == skeletons.end()) { - return nullptr; - } - return found->second.get(); -} - -const rigging::SkeletonConfig& Content::requireSkeleton(const std::string& id) const { - auto skeleton = getSkeleton(id); - if (skeleton == nullptr) { - throw std::runtime_error("skeleton '" + id + "' not loaded"); - } - return *skeleton; -} - const BlockMaterial* Content::findBlockMaterial(const std::string& id) const { auto found = blockMaterials.find(id); if (found == blockMaterials.end()) { @@ -104,8 +84,3 @@ const UptrsMap& Content::getBlockMaterials() const { const UptrsMap& Content::getPacks() const { return packs; } - -const UptrsMap& Content::getSkeletons( -) const { - return skeletons; -} diff --git a/src/content/Content.hpp b/src/content/Content.hpp index 12dc77d2f..dd0e30bf8 100644 --- a/src/content/Content.hpp +++ b/src/content/Content.hpp @@ -20,10 +20,6 @@ struct ItemDef; struct EntityDef; struct GeneratorDef; -namespace rigging { - class SkeletonConfig; -} - class namereuse_error : public std::runtime_error { ContentType type; public: @@ -183,7 +179,6 @@ class Content { std::unique_ptr indices; UptrsMap packs; UptrsMap blockMaterials; - UptrsMap skeletons; dv::value defaults = nullptr; std::unordered_map tags; public: @@ -203,7 +198,6 @@ public: ContentUnitDefs generators, UptrsMap packs, UptrsMap blockMaterials, - UptrsMap skeletons, ResourceIndicesSet resourceIndices, dv::value defaults, std::unordered_map tags @@ -230,13 +224,10 @@ public: return found->second; } - const rigging::SkeletonConfig* getSkeleton(const std::string& id) const; - const rigging::SkeletonConfig& requireSkeleton(const std::string& id) const; const BlockMaterial* findBlockMaterial(const std::string& id) const; const ContentPackRuntime* getPackRuntime(const std::string& id) const; ContentPackRuntime* getPackRuntime(const std::string& id); const UptrsMap& getBlockMaterials() const; const UptrsMap& getPacks() const; - const UptrsMap& getSkeletons() const; }; diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index d32cb2860..ce77434ed 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -1,17 +1,11 @@ #include "ContentBuilder.hpp" -#include "objects/rigging.hpp" - ContentBuilder::~ContentBuilder() = default; void ContentBuilder::add(std::unique_ptr pack) { packs[pack->getId()] = std::move(pack); } -void ContentBuilder::add(std::unique_ptr skeleton) { - skeletons[skeleton->getName()] = std::move(skeleton); -} - BlockMaterial& ContentBuilder::createBlockMaterial(const std::string& id) { blockMaterials[id] = std::make_unique(); auto& material = *blockMaterials[id]; @@ -98,7 +92,6 @@ std::unique_ptr ContentBuilder::build() { generators.build(), std::move(packs), std::move(blockMaterials), - std::move(skeletons), std::move(resourceIndices), std::move(defaults), std::move(tags.map) diff --git a/src/content/ContentBuilder.hpp b/src/content/ContentBuilder.hpp index 8a938819b..b1c4d26c3 100644 --- a/src/content/ContentBuilder.hpp +++ b/src/content/ContentBuilder.hpp @@ -85,7 +85,6 @@ struct TagsIndices { class ContentBuilder { UptrsMap blockMaterials; - UptrsMap skeletons; UptrsMap packs; std::unordered_map allNames; public: @@ -100,7 +99,6 @@ public: ~ContentBuilder(); void add(std::unique_ptr pack); - void add(std::unique_ptr skeleton); BlockMaterial& createBlockMaterial(const std::string& id); diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index ed4ab34d8..e2bf81f2d 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -3,14 +3,12 @@ #include #include -#include #include "loading/ContentUnitLoader.hpp" #include "ContentBuilder.hpp" #include "ContentPack.hpp" #include "debug/Logger.hpp" #include "logic/scripting/scripting.hpp" -#include "objects/rigging.hpp" #include "util/listutil.hpp" #include "util/stringutil.hpp" #include "engine/EnginePaths.hpp" @@ -399,16 +397,6 @@ void ContentLoader::load() { } } - // Load skeletons - io::path skeletonsDir = folder / "skeletons"; - foreach_file(skeletonsDir, [this](const io::path& file) { - std::string name = pack->id + ":" + file.stem(); - std::string text = io::read_string(file); - builder.add( - rigging::SkeletonConfig::parse(text, file.string(), name) - ); - }); - // Process content.json and load defined content units auto contentFile = pack->getContentFile(); if (io::exists(contentFile)) { diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 0f93de8cc..02119b9ad 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -26,6 +26,7 @@ #include "logic/scripting/scripting_hud.hpp" #include "maths/voxmaths.hpp" #include "objects/Players.hpp" +#include "objects/Entities.hpp" #include "physics/Hitbox.hpp" #include "util/stringutil.hpp" #include "voxels/Chunks.hpp" @@ -49,6 +50,7 @@ LevelScreen::LevelScreen( gui(engine.getGUI()), input(engine.getInput()) { Level* level = levelPtr.get(); + level->entities->setAssets(*engine.getAssets()); auto& settings = engine.getSettings(); auto& assets = *engine.getAssets(); diff --git a/src/graphics/commons/Model.cpp b/src/graphics/commons/Model.cpp index e7c247868..2de10ad30 100644 --- a/src/graphics/commons/Model.cpp +++ b/src/graphics/commons/Model.cpp @@ -1,6 +1,10 @@ +#define GLM_ENABLE_EXPERIMENTAL + #include "Model.hpp" #include +#include +#include using namespace model; @@ -71,6 +75,39 @@ void Mesh::addRect( vertices.push_back({pos-right+up, {uv.u1, uv.v2}, norm}); } +static glm::mat4 extract_rotation(const glm::mat4& matrix) { + glm::vec3 scale; + glm::quat rotation; + glm::vec3 translation; + glm::vec3 skew; + glm::vec4 perspective; + glm::decompose(matrix, scale, rotation, translation, skew, perspective); + return glm::mat4_cast(rotation); +} + +void Mesh::addRect( + const glm::vec3& pos, + const glm::vec3& right, + const glm::vec3& up, + const glm::vec3& norm, + const UVRegion& uv, + const glm::mat4& transform +) { + auto a = transform * glm::vec4(pos - right - up, 1.0f); + auto b = transform * glm::vec4(pos + right - up, 1.0f); + auto c = transform * glm::vec4(pos + right + up, 1.0f); + auto d = transform * glm::vec4(pos - right + up, 1.0f); + auto tsfnorm = extract_rotation(transform) * glm::vec4(norm, 1.0f); + + vertices.push_back({a, {uv.u1, uv.v1}, tsfnorm}); + vertices.push_back({b, {uv.u2, uv.v1}, tsfnorm}); + vertices.push_back({c, {uv.u2, uv.v2}, tsfnorm}); + + vertices.push_back({a, {uv.u1, uv.v1}, tsfnorm}); + vertices.push_back({c, {uv.u2, uv.v2}, tsfnorm}); + vertices.push_back({d, {uv.u1, uv.v2}, tsfnorm}); +} + void Mesh::addBox(const glm::vec3& pos, const glm::vec3& size) { UVRegion fullRegion (0, 0, 1, 1); addPlane(pos+Z*size, X*size, Y*size, Z, fullRegion); @@ -119,6 +156,18 @@ void Mesh::scale(const glm::vec3& size) { } } +void Mesh::transform(const glm::mat4& matrix) { + for (auto& vertex : vertices) { + vertex.coord = matrix * glm::vec4(vertex.coord, 1.0f); + } +} + +void Mesh::translate(const glm::vec3& offset) { + for (auto& vertex : vertices) { + vertex.coord += offset; + } +} + void Model::clean() { meshes.erase( std::remove_if(meshes.begin(), meshes.end(), @@ -128,3 +177,41 @@ void Model::clean() { meshes.end() ); } + +void Model::transform(const glm::mat4& matrix) { + for (auto& mesh : meshes) { + mesh.transform(matrix); + } +} + +void Model::translate(const glm::vec3& offset) { + for (auto& mesh : meshes) { + mesh.translate(offset); + } +} + +void Model::merge(const Model& source) { + for (const auto& srcMesh : source.meshes) { + auto& dstMesh = addMesh(srcMesh.texture, srcMesh.shading); + dstMesh.vertices.insert( + dstMesh.vertices.end(), + srcMesh.vertices.begin(), + srcMesh.vertices.end() + ); + } +} + +void Model::merge(Model&& source) { + for (auto& srcMesh : source.meshes) { + auto& dstMesh = addMesh(srcMesh.texture, srcMesh.shading); + if (dstMesh.vertices.empty()) { + dstMesh = std::move(srcMesh); + } else { + dstMesh.vertices.insert( + dstMesh.vertices.end(), + srcMesh.vertices.begin(), + srcMesh.vertices.end() + ); + } + } +} diff --git a/src/graphics/commons/Model.hpp b/src/graphics/commons/Model.hpp index 67618ed38..e42928fba 100644 --- a/src/graphics/commons/Model.hpp +++ b/src/graphics/commons/Model.hpp @@ -40,6 +40,14 @@ namespace model { const glm::vec3& norm, const UVRegion& region ); + void addRect( + const glm::vec3& pos, + const glm::vec3& right, + const glm::vec3& up, + const glm::vec3& norm, + const UVRegion& uv, + const glm::mat4& transform + ); void addTriangle( const glm::vec3& a, const glm::vec3& b, @@ -64,6 +72,12 @@ namespace model { const glm::mat4& transform ); void scale(const glm::vec3& size); + + /// @brief Apply transformation matrix to vertices coordinates + void transform(const glm::mat4& matrix); + + /// @brief Move vertices coordinates by offset + void translate(const glm::vec3& offset); }; struct Model { @@ -81,7 +95,22 @@ namespace model { meshes.push_back({texture, {}, shading}); return meshes[meshes.size() - 1]; } + /// @brief Remove all empty meshes void clean(); + + /// @brief Apply transformation matrix to vertices coordinates + void transform(const glm::mat4& matrix); + + /// @brief Move vertices coordinates by offset + void translate(const glm::vec3& offset); + + /// @brief Copy or merge meshes from source model to this. + /// Does not remove duplicates + void merge(const Model& source); + + /// @brief Move or merge meshes from source model to this. + /// Does not remove duplicates + void merge(Model&& source); }; } diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 70195f927..7b505d868 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -23,6 +23,7 @@ #include "maths/voxmaths.hpp" #include "objects/Entities.hpp" #include "objects/Player.hpp" +#include "objects/rigging.hpp" #include "settings.hpp" #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" @@ -110,7 +111,7 @@ WorldRenderer::WorldRenderer( const auto& content = level.content; skeletons = std::make_unique(); - const auto& skeletonConfig = content.requireSkeleton( + const auto& skeletonConfig = assets->require( content.getDefaults()["hand-skeleton"].asString() ); hands = std::make_unique( diff --git a/src/logic/scripting/lua/libs/lib__skeleton.cpp b/src/logic/scripting/lua/libs/lib__skeleton.cpp index 3fd248725..be42c356e 100644 --- a/src/logic/scripting/lua/libs/lib__skeleton.cpp +++ b/src/logic/scripting/lua/libs/lib__skeleton.cpp @@ -1,6 +1,7 @@ #include "objects/rigging.hpp" #include "libentity.hpp" +#include "engine/Engine.hpp" #include "graphics/render/WorldRenderer.hpp" #include "graphics/render/NamedSkeletons.hpp" @@ -21,11 +22,14 @@ static int index_range_check( } static rigging::Skeleton* get_skeleton(lua::State* L) { + if (scripting::engine->isHeadless()) { + return nullptr; + } if (lua::isstring(L, 1)) { return scripting::renderer->skeletons->getSkeleton(lua::tostring(L, 1)); } if (auto entity = get_entity(L, 1)) { - return &entity->getSkeleton(); + return entity->getSkeleton(); } return nullptr; } diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp index 35720195e..6a16c3cfd 100644 --- a/src/logic/scripting/lua/libs/libassets.cpp +++ b/src/logic/scripting/lua/libs/libassets.cpp @@ -57,11 +57,14 @@ static int l_parse_model(lua::State* L) { auto name = lua::require_string(L, 3); if (format == "xml" || format == "vcm") { + auto vcmModel = vcm::parse(name, string, format == "xml"); engine->getAssets()->store( - vcm::parse(name, string, format == "xml"), name + std::make_unique(std::move(vcmModel.squash())), name ); } else { - throw std::runtime_error("unknown format " + util::quote(std::string(format))); + throw std::runtime_error( + "unknown format " + util::quote(std::string(format)) + ); } return 0; } diff --git a/src/logic/scripting/lua/libs/libentity.cpp b/src/logic/scripting/lua/libs/libentity.cpp index 5bf3e4d74..0ae21f3c7 100644 --- a/src/logic/scripting/lua/libs/libentity.cpp +++ b/src/logic/scripting/lua/libs/libentity.cpp @@ -1,5 +1,6 @@ #include "libentity.hpp" +#include "assets/Assets.hpp" #include "content/Content.hpp" #include "content/ContentPack.hpp" #include "engine/Engine.hpp" @@ -82,7 +83,11 @@ static int l_despawn(lua::State* L) { static int l_get_skeleton(lua::State* L) { if (auto entity = get_entity(L, 1)) { - return lua::pushstring(L, entity->getSkeleton().config->getName()); + auto skeleton = entity->getSkeleton(); + if (skeleton == nullptr) { + return 0; + } + return lua::pushstring(L, skeleton->config->getName()); } return 0; } @@ -97,9 +102,13 @@ static int l_get_player(lua::State* L) { } static int l_set_skeleton(lua::State* L) { + auto assets = engine->getAssets(); + if (assets == nullptr) { + return 0; + } if (auto entity = get_entity(L, 1)) { std::string skeletonName = lua::require_string(L, 2); - auto rigConfig = content->getSkeleton(skeletonName); + auto rigConfig = assets->get(skeletonName); if (rigConfig == nullptr) { throw std::runtime_error( "skeleton not found '" + skeletonName + "'" diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index cf31f5916..179a39fd6 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -1,6 +1,7 @@ #define VC_ENABLE_REFLECTION #include "Entities.hpp" +#include #include #include @@ -25,15 +26,22 @@ static debug::Logger logger("entities"); Entities::Entities(Level& level) - : level(level), + : registry(std::make_unique()), + level(level), sensorsTickClock(20, 3), updateTickClock(20, 3) { } +Entities::~Entities() = default; + +void Entities::setAssets(Assets& assets) { + this->assets = &assets; +} + std::optional Entities::get(entityid_t id) { const auto& found = entities.find(id); - if (found != entities.end() && registry.valid(found->second)) { - return Entity(*this, id, registry, found->second); + if (found != entities.end() && registry->valid(found->second)) { + return Entity(*this, id, *registry, found->second); } return std::nullopt; } @@ -45,9 +53,12 @@ entityid_t Entities::spawn( dv::value saved, entityid_t uid ) { - auto skeleton = level.content.getSkeleton(def.skeletonName); - if (skeleton == nullptr) { - throw std::runtime_error("skeleton " + def.skeletonName + " not found"); + rigging::SkeletonConfig* skeleton = nullptr; + if (assets) { + skeleton = assets->get(def.skeletonName); + if (skeleton == nullptr) { + throw std::runtime_error("skeleton " + def.skeletonName + " not found"); + } } entityid_t id; if (uid == 0) { @@ -64,12 +75,12 @@ entityid_t Entities::spawn( throw std::runtime_error(ss.str()); } } - auto entity = registry.create(); + auto entity = registry->create(); entities[id] = entity; uids[entity] = id; - registry.emplace(entity, static_cast(id), def); - const auto& tsf = registry.emplace( + registry->emplace(entity, static_cast(id), def); + const auto& tsf = registry->emplace( entity, position, glm::vec3(1.0f), @@ -77,7 +88,7 @@ entityid_t Entities::spawn( glm::mat4(1.0f), true ); - auto& body = registry.emplace( + auto& body = registry->emplace( entity, true, Hitbox {def.bodyType, position, def.hitbox * 0.5f}, @@ -85,8 +96,10 @@ entityid_t Entities::spawn( ); body.initialize(def, id, *this); - auto& scripting = registry.emplace(entity); - registry.emplace(entity, skeleton->instance()); + auto& scripting = registry->emplace(entity); + if (assets) { + registry->emplace(entity, skeleton->instance()); + } for (auto& instance : def.components) { auto component = std::make_unique( @@ -126,7 +139,7 @@ void Entities::loadEntity(const dv::value& map) { void Entities::loadEntity(const dv::value& map, Entity entity) { auto& transform = entity.getTransform(); auto& body = entity.getRigidbody(); - auto& skeleton = entity.getSkeleton(); + auto skeleton = entity.getSkeleton(); if (map.has(COMP_RIGIDBODY)) { body.deserialize(map[COMP_RIGIDBODY]); @@ -134,13 +147,16 @@ void Entities::loadEntity(const dv::value& map, Entity entity) { if (map.has(COMP_TRANSFORM)) { transform.deserialize(map[COMP_TRANSFORM]); } - std::string skeletonName = skeleton.config->getName(); - map.at("skeleton").get(skeletonName); - if (skeletonName != skeleton.config->getName()) { - skeleton.config = level.content.getSkeleton(skeletonName); + if (skeleton == nullptr) { + return; + } + std::string skeletonName = skeleton->config->getName(); + map.at("skeleton-name").get(skeletonName); + if (skeletonName != skeleton->config->getName()) { + skeleton->config = assets->get(skeletonName); } if (auto foundSkeleton = map.at(COMP_SKELETON)) { - skeleton.deserialize(*foundSkeleton); + skeleton->deserialize(*foundSkeleton); } } @@ -148,7 +164,7 @@ std::optional Entities::rayCast( glm::vec3 start, glm::vec3 dir, float maxDistance, entityid_t ignore ) { Ray ray(start, dir); - auto view = registry.view(); + auto view = registry->view(); entityid_t foundUID = 0; glm::ivec3 foundNormal; @@ -214,17 +230,17 @@ void Entities::despawn(std::vector entities) { void Entities::clean() { for (auto it = entities.begin(); it != entities.end();) { - if (!registry.get(it->second).destroyFlag) { + if (!registry->get(it->second).destroyFlag) { ++it; } else { - auto& rigidbody = registry.get(it->second); + auto& rigidbody = registry->get(it->second); // todo: refactor auto physics = level.physics.get(); for (auto& sensor : rigidbody.sensors) { physics->removeSensor(&sensor); } uids.erase(it->second); - registry.destroy(it->second); + registry->destroy(it->second); it = entities.erase(it); } } @@ -266,7 +282,7 @@ void Entities::preparePhysics(float delta) { auto part = sensorsTickClock.getPart(); auto parts = sensorsTickClock.getParts(); - auto view = registry.view(); + auto view = registry->view(); auto physics = level.physics.get(); std::vector sensors; for (auto [entity, eid, transform, rigidbody] : view.each()) { @@ -285,7 +301,7 @@ void Entities::preparePhysics(float delta) { void Entities::updatePhysics(float delta) { preparePhysics(delta); - auto view = registry.view(); + auto view = registry->view(); auto physics = level.physics.get(); for (auto [entity, eid, transform, rigidbody] : view.each()) { if (!rigidbody.enabled || rigidbody.hitbox.type == BodyType::STATIC) { @@ -333,7 +349,7 @@ static void debug_render_skeleton( const rigging::Skeleton& skeleton ) { size_t pindex = bone->getIndex(); - for (auto& sub : bone->getSubnodes()) { + for (auto& sub : bone->getBones()) { size_t sindex = sub->getIndex(); const auto& matrices = skeleton.calculated.matrices; batch.line( @@ -351,7 +367,7 @@ void Entities::renderDebug( { auto ctx = pctx.sub(&batch); ctx.setLineWidth(1); - auto view = registry.view(); + auto view = registry->view(); for (auto [entity, transform, rigidbody] : view.each()) { const auto& hitbox = rigidbody.hitbox; const auto& pos = transform.pos; @@ -374,7 +390,7 @@ void Entities::renderDebug( } } { - auto view = registry.view(); + auto view = registry->view(); auto ctx = pctx.sub(&batch); ctx.setDepthTest(false); ctx.setDepthMask(false); @@ -400,7 +416,7 @@ void Entities::render( bool pause, entityid_t fpsEntity ) { - auto view = registry.view(); + auto view = registry->view(); for (auto [entity, eid, transform, skeleton] : view.each()) { if (eid.uid == fpsEntity) { continue; @@ -425,7 +441,7 @@ void Entities::render( } bool Entities::hasBlockingInside(AABB aabb) { - auto view = registry.view(); + auto view = registry->view(); for (auto [entity, eid, body] : view.each()) { if (eid.def.blocking && aabb.intersect(body.hitbox.getAABB(), -0.05f)) { return true; @@ -436,7 +452,7 @@ bool Entities::hasBlockingInside(AABB aabb) { std::vector Entities::getAllInside(AABB aabb) { std::vector collected; - auto view = registry.view(); + auto view = registry->view(); for (auto [entity, eid, transform] : view.each()) { if (!eid.destroyFlag && aabb.contains(transform.pos)) { const auto& found = uids.find(entity); @@ -453,7 +469,7 @@ std::vector Entities::getAllInside(AABB aabb) { std::vector Entities::getAllInRadius(glm::vec3 center, float radius) { std::vector collected; - auto view = registry.view(); + auto view = registry->view(); for (auto [entity, transform] : view.each()) { if (glm::distance2(transform.pos, center) <= radius * radius) { const auto& found = uids.find(entity); diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 03fcec8cb..3b3a7e1a1 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -12,7 +12,7 @@ #include "typedefs.hpp" #include "util/Clock.hpp" -#include +#include #include struct EntityDef; @@ -31,14 +31,15 @@ namespace rigging { class SkeletonConfig; } -class Entities { - entt::registry registry; +class Entities final { + std::unique_ptr registry; Level& level; std::unordered_map entities; std::unordered_map uids; entityid_t nextID = 1; util::Clock sensorsTickClock; util::Clock updateTickClock; + Assets* assets = nullptr; void updateSensors( Rigidbody& body, const Transform& tsf, std::vector& sensors @@ -53,6 +54,10 @@ public: Entities(Level& level); + ~Entities(); + + void setAssets(Assets& assets); + void clean(); void updatePhysics(float delta); void update(float delta); diff --git a/src/objects/Entity.cpp b/src/objects/Entity.cpp index 24aa17362..5fbfe1d99 100644 --- a/src/objects/Entity.cpp +++ b/src/objects/Entity.cpp @@ -13,13 +13,16 @@ static inline std::string SAVED_DATA_VARNAME = "SAVED_DATA"; void Entity::setInterpolatedPosition(const glm::vec3& position) { - getSkeleton().interpolation.refresh(position); + if (auto skeleton = getSkeleton()) { + skeleton->interpolation.refresh(position); + } } glm::vec3 Entity::getInterpolatedPosition() const { - const auto& skeleton = getSkeleton(); - if (skeleton.interpolation.isEnabled()) { - return skeleton.interpolation.getCurrent(); + if (auto skeleton = getSkeleton()) { + if (skeleton->interpolation.isEnabled()) { + return skeleton->interpolation.getCurrent(); + } } return getTransform().pos; } @@ -30,8 +33,8 @@ void Entity::destroy() { } } -rigging::Skeleton& Entity::getSkeleton() const { - return registry.get(entity); +rigging::Skeleton* Entity::getSkeleton() const { + return registry.try_get(entity); } void Entity::setRig(const rigging::SkeletonConfig* rigConfig) { @@ -50,8 +53,8 @@ dv::value Entity::serialize() const { const auto& def = eid.def; const auto& transform = getTransform(); const auto& rigidbody = getRigidbody(); - const auto& skeleton = getSkeleton(); const auto& scripts = getScripting(); + auto skeleton = getSkeleton(); auto root = dv::object(); root["def"] = def.name; @@ -61,13 +64,15 @@ dv::value Entity::serialize() const { root[COMP_RIGIDBODY] = rigidbody.serialize(def.save.body.velocity, def.save.body.settings); - if (skeleton.config->getName() != def.skeletonName) { - root["skeleton"] = skeleton.config->getName(); - } - if (def.save.skeleton.pose || def.save.skeleton.textures) { - root[COMP_SKELETON] = skeleton.serialize( - def.save.skeleton.pose, def.save.skeleton.textures - ); + if (skeleton != nullptr) { + if (skeleton->config->getName() != def.skeletonName) { + root["skeleton-name"] = skeleton->config->getName(); + } + if (def.save.skeleton.pose || def.save.skeleton.textures) { + root[COMP_SKELETON] = skeleton->serialize( + def.save.skeleton.pose, def.save.skeleton.textures + ); + } } if (!scripts.components.empty()) { auto& compsMap = root.object("comps"); diff --git a/src/objects/Entity.hpp b/src/objects/Entity.hpp index 4a306c862..ef00a1bf2 100644 --- a/src/objects/Entity.hpp +++ b/src/objects/Entity.hpp @@ -58,7 +58,7 @@ public: ScriptComponents& getScripting() const; - rigging::Skeleton& getSkeleton() const; + rigging::Skeleton* getSkeleton() const; void setRig(const rigging::SkeletonConfig* rigConfig); diff --git a/src/objects/rigging.cpp b/src/objects/rigging.cpp index da56b26cf..d1a41bef7 100644 --- a/src/objects/rigging.cpp +++ b/src/objects/rigging.cpp @@ -88,7 +88,7 @@ void Skeleton::deserialize(const dv::value& root) { static void get_all_nodes(std::vector& nodes, Bone* node) { nodes[node->getIndex()] = node; - for (auto& subnode : node->getSubnodes()) { + for (auto& subnode : node->getBones()) { get_all_nodes(nodes, subnode.get()); } } @@ -111,7 +111,7 @@ size_t SkeletonConfig::update( } skeleton.calculated.matrices[index] = matrix * baseMatrix * boneMatrix; size_t count = 1; - for (auto& subnode : node->getSubnodes()) { + for (auto& subnode : node->getBones()) { count += update( index + count, skeleton, @@ -187,7 +187,7 @@ void SkeletonConfig::render( } } -Bone* SkeletonConfig::find(std::string_view str) const { +const Bone* SkeletonConfig::find(std::string_view str) const { for (size_t i = 0; i < nodes.size(); i++) { auto* node = nodes[i]; if (node->getName() == str) { diff --git a/src/objects/rigging.hpp b/src/objects/rigging.hpp index c0570c065..7726d0b95 100644 --- a/src/objects/rigging.hpp +++ b/src/objects/rigging.hpp @@ -68,7 +68,7 @@ namespace rigging { return offset; } - const auto& getSubnodes() const { + const auto& getBones() const { return bones; } }; @@ -138,7 +138,7 @@ namespace rigging { return Skeleton(this); } - Bone* find(std::string_view str) const; + const Bone* find(std::string_view str) const; static std::unique_ptr parse( std::string_view src, std::string_view file, std::string_view name