From abb7fe44ca930546c905b7b8f6af0493118343e7 Mon Sep 17 00:00:00 2001 From: "andrey.zhirnov" Date: Wed, 24 Jul 2019 18:54:53 +0300 Subject: [PATCH] added spine components and asset --- .gitattributes | 3 + headers/enduro2d/high/_all.hpp | 2 + headers/enduro2d/high/_high.hpp | 2 + .../high/assets/spine_model_asset.hpp | 21 ++ .../enduro2d/high/components/spine_player.hpp | 52 ++++ .../high/components/spine_renderer.hpp | 57 +++++ headers/enduro2d/high/spine_model.hpp | 58 +++++ .../high/assets/spine_model_asset.cpp | 237 ++++++++++++++++++ .../enduro2d/high/components/spine_player.cpp | 82 ++++++ .../high/components/spine_renderer.cpp | 78 ++++++ sources/enduro2d/high/spine_model.cpp | 120 +++++++++ sources/enduro2d/high/starter.cpp | 6 +- .../render_system_drawer.cpp | 200 ++++++++++++++- .../render_system_drawer.hpp | 13 +- 14 files changed, 926 insertions(+), 5 deletions(-) create mode 100644 headers/enduro2d/high/assets/spine_model_asset.hpp create mode 100644 headers/enduro2d/high/components/spine_player.hpp create mode 100644 headers/enduro2d/high/components/spine_renderer.hpp create mode 100644 headers/enduro2d/high/spine_model.hpp create mode 100644 sources/enduro2d/high/assets/spine_model_asset.cpp create mode 100644 sources/enduro2d/high/components/spine_player.cpp create mode 100644 sources/enduro2d/high/components/spine_renderer.cpp create mode 100644 sources/enduro2d/high/spine_model.cpp diff --git a/.gitattributes b/.gitattributes index eafcbc92..7fef1442 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,3 +12,6 @@ *.pvr filter=lfs diff=lfs merge=lfs -text *.dds filter=lfs diff=lfs merge=lfs -text *.ogg filter=lfs diff=lfs merge=lfs -text +*.skel filter=lfs diff=lfs merge=lfs -text +*.atlas filter=lfs diff=lfs merge=lfs -text +*.json filter=lfs diff=lfs merge=lfs -text diff --git a/headers/enduro2d/high/_all.hpp b/headers/enduro2d/high/_all.hpp index 4c8246a6..17546563 100644 --- a/headers/enduro2d/high/_all.hpp +++ b/headers/enduro2d/high/_all.hpp @@ -33,6 +33,8 @@ #include "components/renderer.hpp" #include "components/scene.hpp" #include "components/sprite_renderer.hpp" +#include "components/spine_renderer.hpp" +#include "components/spine_player.hpp" #include "systems/flipbook_system.hpp" #include "systems/render_system.hpp" diff --git a/headers/enduro2d/high/_high.hpp b/headers/enduro2d/high/_high.hpp index a593f322..e0d44575 100644 --- a/headers/enduro2d/high/_high.hpp +++ b/headers/enduro2d/high/_high.hpp @@ -45,6 +45,8 @@ namespace e2d class renderer; class scene; class sprite_renderer; + class spine_renderer; + class spine_player; class flipbook_system; class render_system; diff --git a/headers/enduro2d/high/assets/spine_model_asset.hpp b/headers/enduro2d/high/assets/spine_model_asset.hpp new file mode 100644 index 00000000..8bfb86e0 --- /dev/null +++ b/headers/enduro2d/high/assets/spine_model_asset.hpp @@ -0,0 +1,21 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#pragma once + +#include "../_high.hpp" + +#include "../library.hpp" +#include "../spine_model.hpp" + +namespace e2d +{ + class spine_model_asset final : public content_asset { + public: + static const char* type_name() noexcept { return "spine_model_asset"; } + static load_async_result load_async(const library& library, str_view address); + }; +} diff --git a/headers/enduro2d/high/components/spine_player.hpp b/headers/enduro2d/high/components/spine_player.hpp new file mode 100644 index 00000000..e87f9cd7 --- /dev/null +++ b/headers/enduro2d/high/components/spine_player.hpp @@ -0,0 +1,52 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#pragma once + +#include "../_high.hpp" + +#include "../factory.hpp" +#include "../assets/spine_model_asset.hpp" + +struct spAnimationState; + +namespace e2d +{ + class spine_player final { + public: + using animation_ptr = std::shared_ptr; + public: + spine_player() = default; + spine_player(const spine_model_asset::ptr& model); + + spine_player& set_animation(int track, const str& name, bool loop); + spine_player& add_animation(int track, const str& name, bool loop, secf delay); + + const animation_ptr& animation() const noexcept; + const spine_model_asset::ptr& model() const noexcept; + private: + animation_ptr animation_; + spine_model_asset::ptr model_; + }; + + template <> + class factory_loader final : factory_loader<> { + public: + static const char* schema_source; + + bool operator()( + spine_player& component, + const fill_context& ctx) const; + + bool operator()( + asset_dependencies& dependencies, + const collect_context& ctx) const; + }; +} + +namespace e2d +{ +} diff --git a/headers/enduro2d/high/components/spine_renderer.hpp b/headers/enduro2d/high/components/spine_renderer.hpp new file mode 100644 index 00000000..431776b3 --- /dev/null +++ b/headers/enduro2d/high/components/spine_renderer.hpp @@ -0,0 +1,57 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#pragma once + +#include "../_high.hpp" + +#include "../factory.hpp" +#include "../assets/spine_model_asset.hpp" + +struct spSkeleton; +struct spSkeletonClipping; +struct spVertexEffect; + +namespace e2d +{ + + class spine_renderer final { + public: + using skeleton_ptr = std::shared_ptr; + using clipping_ptr = std::shared_ptr; + using effect_ptr = std::shared_ptr; + public: + spine_renderer() = default; + spine_renderer(const spine_model_asset::ptr& model); + + spine_renderer& skin(const str& value); + spine_renderer& attachment(const str& slot, const str& name); + + const skeleton_ptr& skeleton() const noexcept; + const clipping_ptr& clipper() const noexcept; + const effect_ptr& effect() const noexcept; + const spine_model_asset::ptr& model() const noexcept; + private: + skeleton_ptr skeleton_; + clipping_ptr clipping_; + effect_ptr effect_; + spine_model_asset::ptr model_; + }; + + template <> + class factory_loader final : factory_loader<> { + public: + static const char* schema_source; + + bool operator()( + spine_renderer& component, + const fill_context& ctx) const; + + bool operator()( + asset_dependencies& dependencies, + const collect_context& ctx) const; + }; +} diff --git a/headers/enduro2d/high/spine_model.hpp b/headers/enduro2d/high/spine_model.hpp new file mode 100644 index 00000000..70f6a5e9 --- /dev/null +++ b/headers/enduro2d/high/spine_model.hpp @@ -0,0 +1,58 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#pragma once + +#include "_high.hpp" + +struct spAnimationStateData; +struct spSkeletonData; +struct spAtlas; + +namespace e2d +{ + + class spine_model final { + public: + using animation_data_ptr = std::shared_ptr; + using skeleton_data_ptr = std::shared_ptr; + using atlas_ptr = std::shared_ptr; + public: + spine_model() noexcept = default; + ~spine_model() noexcept = default; + + spine_model(spine_model&& other) noexcept; + spine_model& operator=(spine_model&& other) noexcept; + + spine_model(const spine_model& other); + spine_model& operator=(const spine_model& other); + + void clear() noexcept; + void swap(spine_model& other) noexcept; + + spine_model& assign(spine_model&& other) noexcept; + spine_model& assign(const spine_model& other); + + spine_model& set_skeleton(skeleton_data_ptr data); + spine_model& set_atlas(atlas_ptr atlas, bool premultiplied_alpha); + + spine_model& mix_animations(const str& from, const str& to, secf duration); + + const atlas_ptr& atlas() const noexcept; + const animation_data_ptr& animation() const noexcept; + const skeleton_data_ptr& skeleton() const noexcept; + bool premultiplied_alpha() const noexcept; + private: + animation_data_ptr animation_; + skeleton_data_ptr skeleton_; + atlas_ptr atlas_; + bool premultiplied_alpha_ = false; + }; + + void swap(spine_model& l, spine_model& r) noexcept; + bool operator==(const spine_model& l, const spine_model& r) noexcept; + bool operator!=(const spine_model& l, const spine_model& r) noexcept; +} diff --git a/sources/enduro2d/high/assets/spine_model_asset.cpp b/sources/enduro2d/high/assets/spine_model_asset.cpp new file mode 100644 index 00000000..473ab5d3 --- /dev/null +++ b/sources/enduro2d/high/assets/spine_model_asset.cpp @@ -0,0 +1,237 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#include + +#include +#include +#include + +#include +#include + +using namespace e2d; + +namespace +{ + class spine_model_asset_loading_exception final : public asset_loading_exception { + const char* what() const noexcept final { + return "spine model asset loading exception"; + } + }; + + const char* spine_model_asset_schema_source = R"json({ + "type" : "object", + "required" : [ "skeleton", "atlas" ], + "additionalProperties" : false, + "properties" : { + "skeleton" : { "$ref" : "#/common_definitions/address" }, + "atlas" : { "$ref" : "#/common_definitions/address" }, + "premultiplied_alpha" : { "type" : "boolean" }, + "mix_animations" : { + "type" : "array", + "items" : { "$ref": "#/definitions/spine_animation_mix_array" } + } + }, + "definitions" : { + "spine_animation_mix_array" : { + "type" : "array", + "items" : { "$ref": "#/definitions/spine_animation_mix" } + }, + "spine_animation_mix" : { + "type" : "object", + "required" : [ "from_anim", "to_anim", "duration" ], + "additionalProperties" : false, + "properties" : { + "from_anim" : { "$ref": "#/common_definitions/name" }, + "to_anim" : { "$ref": "#/common_definitions/name" }, + "duration" : { "type" : "number" } + } + } + } + })json"; + + const rapidjson::SchemaDocument& spine_model_asset_schema() { + static std::mutex mutex; + static std::unique_ptr schema; + + std::lock_guard guard(mutex); + if ( !schema ) { + rapidjson::Document doc; + if ( doc.Parse(spine_model_asset_schema_source).HasParseError() ) { + the().error("SPINE: Failed to parse spine model asset schema"); + throw spine_model_asset_loading_exception(); + } + json_utils::add_common_schema_definitions(doc); + schema = std::make_unique(doc); + } + + return *schema; + } + + bool parse_mix_animations( + const rapidjson::Value& root, + spine_model& model) + { + E2D_ASSERT(root.IsArray()); + str from_anim; + str to_anim; + secf duration; + + for ( rapidjson::SizeType i = 0; i < root.Size(); ++i ) { + E2D_ASSERT(root[i].IsObject()); + const auto& sprite_json = root[i]; + + E2D_ASSERT(sprite_json.HasMember("from_anim")); + if ( !json_utils::try_parse_value(sprite_json["from_anim"], from_anim) ) { + the().error("SPINE: Incorrect formatting of 'from_anim' property"); + return false; + } + + E2D_ASSERT(sprite_json.HasMember("to_anim")); + if ( !json_utils::try_parse_value(sprite_json["to_anim"], to_anim) ) { + the().error("SPINE: Incorrect formatting of 'to_anim' property"); + return false; + } + + E2D_ASSERT(sprite_json.HasMember("duration")); + if ( !json_utils::try_parse_value(sprite_json["duration"], duration.value) ) { + the().error("SPINE: Incorrect formatting of 'duration' property"); + return false; + } + + model.mix_animations(from_anim, to_anim, duration); + } + return true; + } + + stdex::promise parse_spine_model( + const library& library, + const str& parent_address, + const rapidjson::Value& root) + { + using skeleton_json_ptr = std::unique_ptr; + stdex::promise result; + + E2D_ASSERT(root.HasMember("atlas") && root["atlas"].IsString()); + binary_asset::load_result atlas_data = library.load_asset( + path::combine(parent_address, root["atlas"].GetString())); + spine_model::atlas_ptr atlas( + spAtlas_create( + reinterpret_cast(atlas_data->content().data()), + math::numeric_cast(atlas_data->content().size()), + parent_address.data(), + nullptr), + spAtlas_dispose); + + E2D_ASSERT(root.HasMember("skeleton") && root["skeleton"].IsString()); + binary_asset::load_result skeleton_data = library.load_asset( + path::combine(parent_address, root["skeleton"].GetString())); + skeleton_json_ptr json( + spSkeletonJson_create(atlas.get()), + spSkeletonJson_dispose); + spine_model::skeleton_data_ptr skeleton( + spSkeletonJson_readSkeletonData( + json.get(), + reinterpret_cast(skeleton_data->content().data())), + spSkeletonData_dispose); + + bool pma = false; + if ( root.HasMember("premultiplied_alpha") ) { + if ( !json_utils::try_parse_value(root["premultiplied_alpha"], pma) ) { + the().error("SPINE: Incorrect formating of 'premultiplied_alpha' property"); + } + } + + spine_model content; + content.set_atlas(atlas, pma); + content.set_skeleton(skeleton); + + if ( root.HasMember("mix_animations") ) { + const auto& mix_animations_json = root["mix_animations"]; + if ( !parse_mix_animations(mix_animations_json, content) ) { + result.reject(spine_model_asset_loading_exception()); + return result; + } + } + + result.resolve(std::move(content)); + return result; + } +} + +void _spAtlasPage_createTexture (spAtlasPage* self, const char* path) { + try { + texture_asset::load_result texture = the().load_asset(path); + if ( !texture ) { + throw; + } + self->width = texture->content()->size().x; + self->height = texture->content()->size().y; + self->rendererObject = texture.release(); + } catch(...) { + the().error("SPINE: Failed to load atlas texture"); + } +} + +void _spAtlasPage_disposeTexture (spAtlasPage* self) { + // acquire by smart pointer and release + texture_asset::ptr(static_cast(self->rendererObject), false); +} + +char* _spUtil_readFile (const char* path, int* length) { + E2D_ASSERT(false); + return _spReadFile(path, length); +} + +namespace e2d +{ + spine_model_asset::load_async_result spine_model_asset::load_async( + const library& library, str_view address) + { + return library.load_asset_async(address) + .then([ + &library, + address = str(address), + parent_address = path::parent_path(address) + ](const json_asset::load_result& spine_data){ + return the().do_in_worker_thread([address, spine_data](){ + const rapidjson::Document& doc = *spine_data->content(); + rapidjson::SchemaValidator validator(spine_model_asset_schema()); + + if ( doc.Accept(validator) ) { + return; + } + + rapidjson::StringBuffer sb; + if ( validator.GetInvalidDocumentPointer().StringifyUriFragment(sb) ) { + the().error("ASSET: Failed to validate asset json:\n" + "--> Address: %0\n" + "--> Invalid schema keyword: %1\n" + "--> Invalid document pointer: %2", + address, + validator.GetInvalidSchemaKeyword(), + sb.GetString()); + } else { + the().error("ASSET: Failed to validate asset json"); + } + + throw spine_model_asset_loading_exception(); + }) + .except([](auto&){ + E2D_ASSERT(false); + }) + .then([&library, parent_address, spine_data](){ + return parse_spine_model( + library, parent_address, *spine_data->content()); + }) + .then([](auto&& content){ + return spine_model_asset::create( + std::forward(content)); + }); + }); + } +} diff --git a/sources/enduro2d/high/components/spine_player.cpp b/sources/enduro2d/high/components/spine_player.cpp new file mode 100644 index 00000000..df7a8991 --- /dev/null +++ b/sources/enduro2d/high/components/spine_player.cpp @@ -0,0 +1,82 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#include + +#include + +namespace e2d +{ + spine_player::spine_player(const spine_model_asset::ptr& model) + : model_(model) { + E2D_ASSERT(model_); + E2D_ASSERT(model_->content().animation()); + + animation_ = animation_ptr(spAnimationState_create(model_->content().animation().get()), spAnimationState_dispose); + } + + spine_player& spine_player::set_animation(int track, const str& name, bool loop) { + E2D_ASSERT(model_); + spAnimation* anim = spSkeletonData_findAnimation(model_->content().skeleton().operator->(), name.c_str()); + if ( !anim ) { + E2D_ASSERT(false); // TODO: exception? + return *this; + } + spAnimationState_setAnimation(animation_.get(), track, anim, loop); + return *this; + } + + spine_player& spine_player::add_animation(int track, const str& name, bool loop, secf delay) { + E2D_ASSERT(model_); + spAnimation* anim = spSkeletonData_findAnimation(model_->content().skeleton().operator->(), name.c_str()); + if ( !anim ) { + E2D_ASSERT(false); // TODO: exception? + return *this; + } + spAnimationState_addAnimation(animation_.get(), track, anim, loop, delay.value); + return *this; + } + + const spine_player::animation_ptr& spine_player::animation() const noexcept { + return animation_; + } + + const spine_model_asset::ptr& spine_player::model() const noexcept { + return model_; + } +} + +namespace e2d +{ + const char* factory_loader::schema_source = R"json({ + "type" : "object", + "required" : [], + "additionalProperties" : false, + "properties" : { + "time" : { "type" : "number" }, + "speed" : { "type" : "number" }, + "looped" : { "type" : "boolean" }, + "stopped" : { "type" : "boolean" }, + "playing" : { "type" : "boolean" }, + "sequence" : { "$ref": "#/common_definitions/name" } + } + })json"; + + bool factory_loader::operator()( + spine_player& component, + const fill_context& ctx) const + { + return false; + } + + bool factory_loader::operator()( + asset_dependencies& dependencies, + const collect_context& ctx) const + { + E2D_UNUSED(dependencies, ctx); + return true; + } +} diff --git a/sources/enduro2d/high/components/spine_renderer.cpp b/sources/enduro2d/high/components/spine_renderer.cpp new file mode 100644 index 00000000..ef230d71 --- /dev/null +++ b/sources/enduro2d/high/components/spine_renderer.cpp @@ -0,0 +1,78 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#include +#include +#include +#include + +namespace e2d +{ + spine_renderer::spine_renderer(const spine_model_asset::ptr& model) + : model_(model) { + E2D_ASSERT(model_); + E2D_ASSERT(model_->content().skeleton()); + E2D_ASSERT(model_->content().atlas()); + + skeleton_ = skeleton_ptr(spSkeleton_create(model_->content().skeleton().get()), spSkeleton_dispose); + clipping_ = clipping_ptr(spSkeletonClipping_create(), spSkeletonClipping_dispose); + } + + spine_renderer& spine_renderer::skin(const str& value) { + spSkeleton_setSkinByName(skeleton_.get(), value.empty() ? nullptr : value.c_str()); + return *this; + } + + spine_renderer& spine_renderer::attachment(const str& slot, const str& name) { + E2D_ASSERT(!slot.empty()); + E2D_ASSERT(!name.empty()); + spSkeleton_setAttachment(skeleton_.get(), slot.c_str(), name.c_str()); // TODO: check result + return *this; + } + + const spine_renderer::skeleton_ptr& spine_renderer::skeleton() const noexcept { + return skeleton_; + } + + const spine_renderer::clipping_ptr& spine_renderer::clipper() const noexcept { + return clipping_; + } + + const spine_renderer::effect_ptr& spine_renderer::effect() const noexcept { + return effect_; + } + + const spine_model_asset::ptr& spine_renderer::model() const noexcept { + return model_; + } +} + +namespace e2d +{ + const char* factory_loader::schema_source = R"json({ + "type" : "object", + "required" : [ "model" ], + "additionalProperties" : false, + "properties" : { + "model" : { "$ref": "#/common_definitions/address" }, + "skin" : { "$ref": "#/common_definitions/name" } + } + })json"; + + bool factory_loader::operator()( + spine_renderer& component, + const fill_context& ctx) const + { + return false; + } + + bool factory_loader::operator()( + asset_dependencies& dependencies, + const collect_context& ctx) const + { + return false; + } +} diff --git a/sources/enduro2d/high/spine_model.cpp b/sources/enduro2d/high/spine_model.cpp new file mode 100644 index 00000000..c78b54aa --- /dev/null +++ b/sources/enduro2d/high/spine_model.cpp @@ -0,0 +1,120 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com) + ******************************************************************************/ + +#include +#include +#include +#include + +namespace e2d +{ + spine_model::spine_model(spine_model&& other) noexcept { + assign(std::move(other)); + } + + spine_model& spine_model::operator=(spine_model&& other) noexcept { + return assign(std::move(other)); + } + + spine_model::spine_model(const spine_model& other) { + assign(other); + } + + spine_model& spine_model::operator=(const spine_model& other) { + return assign(other); + } + + void spine_model::clear() noexcept { + animation_.reset(); + skeleton_.reset(); + atlas_.reset(); + } + + void spine_model::swap(spine_model& other) noexcept { + using std::swap; + swap(animation_, other.animation_); + swap(skeleton_, other.skeleton_); + swap(atlas_, other.atlas_); + swap(premultiplied_alpha_, other.premultiplied_alpha_); + } + + spine_model& spine_model::assign(spine_model&& other) noexcept { + if ( this != &other ) { + swap(other); + other.clear(); + } + return *this; + } + + spine_model& spine_model::assign(const spine_model& other) { + if ( this != &other ) { + spine_model m; + m.animation_ = other.animation_; + m.skeleton_ = other.skeleton_; + m.atlas_ = other.atlas_; + m.premultiplied_alpha_ = other.premultiplied_alpha_; + swap(m); + } + return *this; + } + + spine_model& spine_model::set_skeleton(skeleton_data_ptr data) { + animation_.reset(); + skeleton_ = std::move(data); + if ( skeleton_ ) { + animation_ = animation_data_ptr( + spAnimationStateData_create(skeleton_.get()), + spAnimationStateData_dispose); + } + return *this; + } + + spine_model& spine_model::set_atlas(atlas_ptr atlas, bool premultiplied_alpha) { + atlas_ = std::move(atlas); + premultiplied_alpha_ = premultiplied_alpha; + return *this; + } + + spine_model& spine_model::mix_animations(const str& from, const str& to, secf duration) { + E2D_ASSERT(animation_); + spAnimationStateData_setMixByName(animation_.get(), from.c_str(), to.c_str(), duration.value); + return *this; + } + + const spine_model::atlas_ptr& spine_model::atlas() const noexcept { + return atlas_; + } + + const spine_model::animation_data_ptr& spine_model::animation() const noexcept { + return animation_; + } + + const spine_model::skeleton_data_ptr& spine_model::skeleton() const noexcept { + return skeleton_; + } + + bool spine_model::premultiplied_alpha() const noexcept { + return premultiplied_alpha_; + } +} + +namespace e2d +{ + void swap(spine_model& l, spine_model& r) noexcept { + l.swap(r); + } + + bool operator==(const spine_model& l, const spine_model& r) noexcept { + return l.atlas() == r.atlas() + && l.animation() == r.animation() + && l.skeleton() == r.skeleton() + && l.premultiplied_alpha() == r.premultiplied_alpha(); + } + + bool operator!=(const spine_model& l, const spine_model& r) noexcept { + return !(l == r); + } +} diff --git a/sources/enduro2d/high/starter.cpp b/sources/enduro2d/high/starter.cpp index a923837f..0b9e4b45 100644 --- a/sources/enduro2d/high/starter.cpp +++ b/sources/enduro2d/high/starter.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #include @@ -133,7 +135,9 @@ namespace e2d .register_component("model_renderer") .register_component("renderer") .register_component("scene") - .register_component("sprite_renderer"); + .register_component("sprite_renderer") + .register_component("spine_renderer") + .register_component("spine_player"); safe_module_initialize(params.library_root(), the()); safe_module_initialize(); } diff --git a/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.cpp b/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.cpp index 751ec273..37bf923d 100644 --- a/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.cpp +++ b/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.cpp @@ -9,6 +9,15 @@ #include #include #include +#include +#include + +#include +#include +#include +#include +#include +#include namespace { @@ -32,9 +41,11 @@ namespace e2d::render_system_impl const const_node_iptr& cam_n, engine& engine, render& render, - batcher_type& batcher) + batcher_type& batcher, + std::vector& spine_vertices) : render_(render) , batcher_(batcher) + , spine_vertices_(spine_vertices) { const m4f& cam_w = cam_n ? cam_n->world_matrix() @@ -83,6 +94,11 @@ namespace e2d::render_system_impl if ( spr_r ) { draw(node, *node_r, *spr_r); } + const spine_renderer* spine_r = node_e.find_component(); + const spine_player* spine_p = node_e.find_component(); + if ( spine_r ) { + draw(node, *node_r, *spine_r, spine_p); + } } } @@ -210,6 +226,184 @@ namespace e2d::render_system_impl } property_cache_.clear(); } + + void drawer::context::draw( + const const_node_iptr& node, + const renderer& node_r, + const spine_renderer& spine_r, + const spine_player* spine_p) + { + static_assert(sizeof(batcher_type::vertex_type) % sizeof(float) == 0, "invalid stride"); + constexpr int stride = sizeof(batcher_type::vertex_type) / sizeof(float); + + if ( !node || !node_r.enabled() ) { + return; + } + + if ( node_r.materials().empty() ) { + return; + } + + spSkeleton* skeleton = spine_r.skeleton().operator->(); + spAnimationState* anim_state = spine_p ? spine_p->animation().operator->() : nullptr; + spSkeletonClipping* clipper = spine_r.clipper().operator->(); + spVertexEffect* effect = spine_r.effect().operator->(); + const material_asset::ptr& mat_a = node_r.materials().front(); + float dt = 0.01f; + + if ( !skeleton || !clipper || !mat_a ) { + return; + } + + spSkeleton_update(skeleton, dt); + if ( anim_state ) { + spAnimationState_update(anim_state, dt); + spAnimationState_apply(anim_state, skeleton); + } + spSkeleton_updateWorldTransform(skeleton); + + const u16 quad_indices[6] = { 0, 1, 2, 2, 3, 0 }; + + if ( skeleton->color.a == 0 ) { + return; + } + + if ( effect ) { + effect->begin(effect, skeleton); + } + + for ( int i = 0; i < skeleton->slotsCount; ++i ) { + spSlot* slot = skeleton->drawOrder[i]; + spAttachment* attachment = slot->attachment; + if ( !attachment ) { + continue; + } + if ( slot->color.a == 0 ) { + spSkeletonClipping_clipEnd(clipper, slot); + continue; + } + + int vertex_count = 0; + const float* uvs = nullptr; + const u16* indices = nullptr; + int index_count = 0; + const spColor* attachment_color; + texture_ptr texture; + + if ( attachment->type == SP_ATTACHMENT_REGION ) { + spRegionAttachment* region = reinterpret_cast(attachment); + attachment_color = ®ion->color; + + if ( attachment_color->a == 0 ) { + spSkeletonClipping_clipEnd(clipper, slot); + continue; + } + spRegionAttachment_computeWorldVertices(region, slot->bone, &spine_vertices_.data()->v.x, 0, stride); + vertex_count = 4; + uvs = region->uvs; + indices = quad_indices; + index_count = 6; + if ( texture_asset* asset = static_cast(static_cast(region->rendererObject)->page->rendererObject) ) { + texture = asset->content(); + } + } else if ( attachment->type == SP_ATTACHMENT_MESH ) { + spMeshAttachment* mesh = reinterpret_cast(attachment); + attachment_color = &mesh->color; + + if ( attachment_color->a == 0 ) { + spSkeletonClipping_clipEnd(clipper, slot); + continue; + } + vertex_count = mesh->super.worldVerticesLength >> 1; + if ( vertex_count > spine_vertices_.size() ) { + spine_vertices_.resize(vertex_count); + } + spVertexAttachment_computeWorldVertices(&mesh->super, slot, 0, mesh->super.worldVerticesLength, &spine_vertices_.data()->v.x, 0, stride); + uvs = mesh->uvs; + indices = mesh->triangles; + index_count = mesh->trianglesCount; + if ( texture_asset* asset = static_cast(static_cast(mesh->rendererObject)->page->rendererObject) ) { + texture = asset->content(); + } + } else if ( attachment->type == SP_ATTACHMENT_CLIPPING ) { + spClippingAttachment* clip = reinterpret_cast(attachment); + spSkeletonClipping_clipStart(clipper, slot, clip); + E2D_ASSERT(false); + continue; + } else { + continue; + } + + const color32 color( + color(skeleton->color.r, skeleton->color.b, skeleton->color.g, skeleton->color.a) * + color(slot->color.r, slot->color.b, slot->color.g, slot->color.a) * + color(attachment_color->r, attachment_color->g, attachment_color->b, attachment_color->a)); + + switch ( slot->data->blendMode ) { + case SP_BLEND_MODE_NORMAL : + break; + case SP_BLEND_MODE_ADDITIVE : + break; + case SP_BLEND_MODE_MULTIPLY : + break; + case SP_BLEND_MODE_SCREEN : + break; + default : + break; + } + + /*const float* vertices = spine_vertices_.data(); + if ( spSkeletonClipping_isClipping(clipper) ) { + spSkeletonClipping_clipTriangles(clipper, vertices, vertex_count, indices, index_count, uvs, 2); + vertices = clipper->clippedVertices->items; + vertex_count = clipper->clippedVertices->size >> 1; + uvs = clipper->clippedUVs->items; + indices = clipper->clippedTriangles->items; + index_count = clipper->clippedTriangles->size; + }*/ + + if ( effect ) { + E2D_ASSERT(false); + } else { + for ( int i = 0; i < index_count; ++i ) { + int index = indices[i] << 1; + auto& vert = spine_vertices_[indices[i]]; + vert.v.z = 0.0f; + vert.t.x = uvs[index]; + vert.t.y = uvs[index+1]; + vert.c = color; + } + } + + try { + property_cache_ + .sampler(sprite_texture_sampler_hash, render::sampler_state() + .texture(texture) + .min_filter(render::sampler_min_filter::linear) + .mag_filter(render::sampler_mag_filter::linear)) + .merge(node_r.properties()); + + batcher_.batch( + mat_a, + property_cache_, + indices, index_count, + spine_vertices_.data(), vertex_count); + } catch (...) { + property_cache_.clear(); + throw; + } + + spSkeletonClipping_clipEnd(clipper, slot); + } + + spSkeletonClipping_clipEnd2(clipper); + + if ( effect ) { + effect->end(effect); + } + + property_cache_.clear(); + } void drawer::context::flush() { batcher_.flush(); @@ -222,5 +416,7 @@ namespace e2d::render_system_impl drawer::drawer(engine& e, debug& d, render& r) : engine_(e) , render_(r) - , batcher_(d, r) {} + , batcher_(d, r) { + spine_vertices_.resize(256); + } } diff --git a/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.hpp b/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.hpp index 641e8136..05c82cff 100644 --- a/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.hpp +++ b/sources/enduro2d/high/systems/render_system_impl/render_system_drawer.hpp @@ -36,7 +36,8 @@ namespace e2d::render_system_impl const const_node_iptr& cam_n, engine& engine, render& render, - batcher_type& batcher); + batcher_type& batcher, + std::vector& spine_vertices); ~context() noexcept; void draw( @@ -51,11 +52,18 @@ namespace e2d::render_system_impl const const_node_iptr& node, const renderer& node_r, const sprite_renderer& spr_r); + + void draw( + const const_node_iptr& node, + const renderer& node_r, + const spine_renderer& spine_r, + const spine_player* spine_p); void flush(); private: render& render_; batcher_type& batcher_; + std::vector& spine_vertices_; render::property_block property_cache_; }; public: @@ -67,6 +75,7 @@ namespace e2d::render_system_impl engine& engine_; render& render_; batcher_type batcher_; + std::vector spine_vertices_; }; } @@ -74,7 +83,7 @@ namespace e2d::render_system_impl { template < typename F > void drawer::with(const camera& cam, const const_node_iptr& cam_n, F&& f) { - context ctx{cam, cam_n, engine_, render_, batcher_}; + context ctx{cam, cam_n, engine_, render_, batcher_, spine_vertices_}; std::forward(f)(ctx); ctx.flush(); }