Created
October 1, 2025 13:55
-
-
Save hsdk123/6b32fc70b92b5ac9301ffd0acf92d782 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "NSprite_Spine.h" | |
| #include <spine/Extension.h> | |
| #include <spine/TextureLoader.h> | |
| #include <spine/SkeletonJson.h> | |
| #include <spine/SkeletonBinary.h> | |
| #include <spine/Vertices.h> | |
| #include <spine/Bone.h> | |
| using namespace lvn; | |
| spine::SpineExtension* spine::getDefaultExtension() { | |
| return new spine::DefaultSpineExtension(); | |
| } | |
| namespace { | |
| class MyTextureLoader : public spine::TextureLoader { | |
| public: | |
| MyTextureLoader() {} | |
| ~MyTextureLoader() override {} | |
| // Called when the atlas loads the texture of a page. | |
| void load(spine::AtlasPage& page, const spine::String& path) override { | |
| //Texture* texture = engine_loadTexture(path); | |
| const auto filepath = NHelper::s2ws(path.buffer()); | |
| const auto resource = CTextureMgr::GetInstance().GetResource(filepath); | |
| if (!resource) { | |
| return; | |
| } | |
| _cache[resource->getFilepath().string()] = resource; | |
| // store the Texture on the rendererObject so we can | |
| // retrieve it later for rendering. | |
| page.texture = resource.get(); | |
| } | |
| // Called when the atlas is disposed and itself disposes its atlas pages. | |
| void unload(void* texture_) override | |
| { | |
| if (!texture_) { | |
| return; | |
| } | |
| auto texture = static_cast<lvn::Texture*>(texture_); | |
| _cache.erase(texture->getFilepath().string()); | |
| } | |
| private: | |
| // cache s.t. texturemgr doesn't release | |
| std::unordered_map<tstring, std::shared_ptr<lvn::Texture>> _cache; | |
| }; | |
| lvn::BlendMode to_lvn_blendmode(const spine::BlendMode& mode_) | |
| { | |
| if (mode_ == spine::BlendMode::BlendMode_Normal) { | |
| return lvn::BlendMode::BlendAlpha; | |
| } | |
| if (mode_ == spine::BlendMode::BlendMode_Additive) { | |
| return lvn::BlendMode::BlendAdd; | |
| } | |
| if (mode_ == spine::BlendMode::BlendMode_Multiply) { | |
| return lvn::BlendMode::BlendMultiply; | |
| // the equations in the spine link don't seem correct... | |
| /*return lvn::BlendMode( | |
| BlendMode::Factor::DestinationColor, BlendMode::Factor::OneMinusSourceAlpha, BlendMode::Equation::Add, | |
| BlendMode::Factor::OneMinusSourceAlpha, BlendMode::Factor::OneMinusSourceAlpha, BlendMode::Equation::Add | |
| );*/ | |
| } | |
| if (mode_ == spine::BlendMode::BlendMode_Screen) { | |
| return lvn::BlendMode::BlendScreen; | |
| /*return lvn::BlendMode( | |
| BlendMode::Factor::One, BlendMode::Factor::OneMinusSourceColor, BlendMode::Equation::Add, | |
| BlendMode::Factor::OneMinusSourceColor, BlendMode::Factor::OneMinusSourceColor, BlendMode::Equation::Add | |
| );*/ | |
| } | |
| CLOG_ERROR(L"to_lvn_blendmode unhandled: {}", static_cast<int>(mode_)); | |
| return lvn::BlendMode::BlendAlpha; | |
| } | |
| } | |
| NSprite_Spine::NSprite_Spine( const tstring& name ) : | |
| StateObj( NType::extern_spine_cg, name ) | |
| { | |
| } | |
| NSprite_Spine::~NSprite_Spine() | |
| { | |
| // in this order (due to dependencies on one another) | |
| _spine_skeleton.reset(); | |
| _spine_animation_state.reset(); | |
| _spine_atlas.reset(); | |
| _spine_skeleton_data.reset(); | |
| _spine_animation_state_data.reset(); | |
| } | |
| std::shared_ptr<NSprite_Spine> lvn::NSprite_Spine::Create( | |
| const tstring& name, | |
| const tstring& filepath_skeleton_, | |
| const tstring& filepath_atlas_, | |
| const tstring& start_animation, | |
| const bool repeat | |
| ) | |
| { | |
| using namespace std; | |
| auto obj = make_unique<NSprite_Spine>(name); | |
| { | |
| auto& state = obj->GetState(); | |
| auto& obj_data = obj->getData(); | |
| { | |
| obj_data.filepath_skeleton = filepath_skeleton_; | |
| obj_data.filepath_atlas = filepath_atlas_; | |
| } | |
| const auto filepath_atlas = NFilepath::EvalLvnPath(NConstants::imageDir.first, filepath_atlas_).string(); | |
| const auto texture_directory = filepath_atlas.substr(0, filepath_atlas.find_last_of(L'/')); | |
| // static state | |
| { | |
| { | |
| const auto stream = CStreamLoadMgr::GetInstance().get_stream_createIfNone(filepath_atlas); | |
| if (!stream) { | |
| CLOG_ERROR(L"filepath: {} does not exist", filepath_atlas); | |
| return nullptr; | |
| } | |
| std::string resource_directory = NHelper::ws2s(texture_directory); | |
| obj->_texture_loader = std::make_shared<MyTextureLoader>(); | |
| obj->_spine_atlas = std::make_shared<spine::Atlas>( | |
| stream->getData().data(), stream->getData().length(), resource_directory.c_str(), obj->_texture_loader.get()); | |
| } | |
| { | |
| const auto filepath_skeleton = NFilepath::EvalLvnPath(NConstants::imageDir.first, filepath_skeleton_).string(); | |
| const auto stream = CStreamLoadMgr::GetInstance().get_stream_createIfNone(filepath_skeleton); | |
| if (!stream) { | |
| CLOG_ERROR(L"filepath: {} does not exist", filepath_skeleton); | |
| return nullptr; | |
| } | |
| if (filepath_skeleton.find(L"json") != tstring::npos) { | |
| spine::SkeletonJson json(obj->_spine_atlas.get()); | |
| const auto p_skeleton_data = json.readSkeletonData(stream->getData().data()); | |
| if (!p_skeleton_data) { | |
| CLOG_ERROR(L"skeleton data: {} could not be loaded", filepath_skeleton); | |
| return nullptr; | |
| } | |
| obj->_spine_skeleton_data = std::shared_ptr<spine::SkeletonData>(p_skeleton_data); | |
| } | |
| else { | |
| spine::SkeletonBinary binary(obj->_spine_atlas.get()); | |
| const auto p_skeleton_data = binary.readSkeletonData( | |
| reinterpret_cast<const unsigned char*>(stream->getData().data()), stream->getData().size()); | |
| if (!p_skeleton_data) { | |
| CLOG_ERROR(L"skeleton data: {} could not be loaded", filepath_skeleton); | |
| return nullptr; | |
| } | |
| obj->_spine_skeleton_data = std::shared_ptr<spine::SkeletonData>(p_skeleton_data); | |
| } | |
| } | |
| { | |
| auto& state_data = obj->_spine_animation_state_data; | |
| state_data = std::make_shared<spine::AnimationStateData>(obj->_spine_skeleton_data.get()); | |
| state_data->setDefaultMix(0.5f); | |
| } | |
| } | |
| // dynamic state | |
| { | |
| obj->_spine_skeleton = std::make_shared< spine::Skeleton>(obj->_spine_skeleton_data.get()); | |
| obj->_spine_animation_state = std::make_shared<spine::AnimationState>(obj->_spine_animation_state_data.get()); | |
| const auto start_animation_ = NHelper::ws2s(start_animation); | |
| { | |
| state.GetComponent<CState_Animation>()._state_name = start_animation; | |
| state.GetComponent<CState_Animation>()._repeat = repeat; | |
| if (!obj->SyncState()) { | |
| return nullptr; | |
| } | |
| } | |
| } | |
| // spine's y axis is upside down to us. | |
| spine::Bone::setYDown(true); | |
| // update once | |
| obj->Update(0); | |
| } | |
| return obj; | |
| } | |
| // reference: | |
| // https://en.esotericsoftware.com/spine-cpp#Putting-it-all-together | |
| void NSprite_Spine::Update(const lvn::TimeUnit delta_ms) | |
| { | |
| StateObj::Update(delta_ms); | |
| auto& state = GetState(); | |
| // sync with playable. | |
| if (state.GetComponent<CPlayable>().GetPlayState() == PlayState::paused) { | |
| return; | |
| } | |
| auto& data = getData(); | |
| { | |
| // update to new animation if needed | |
| bool animation_updated = false; | |
| { | |
| auto& state_animation = state.GetComponent<CState_Animation>(); | |
| if (state_animation._state_name != data.animation_state_name) { | |
| animation_updated = SyncState(); | |
| } | |
| } | |
| const auto delta_secs = animation_updated ? 0.f : (delta_ms / 1000.f); | |
| // update spine states | |
| // https://en.esotericsoftware.com/spine-cpp#Updating-and-applying | |
| { | |
| _spine_animation_state->update(delta_secs); | |
| _spine_animation_state->apply(*_spine_skeleton); | |
| _spine_skeleton->update(delta_secs); | |
| _spine_skeleton->updateWorldTransform(spine::Physics_Update); | |
| } | |
| } | |
| } | |
| bool NSprite_Spine::SyncState() | |
| { | |
| auto& state = GetState(); | |
| auto& data = getData(); | |
| auto& state_animation = state.GetComponent<CState_Animation>(); | |
| const auto initial_sync = (data.animation_state_name.empty()); | |
| { | |
| const auto new_animation_state = NHelper::ws2s(state_animation._state_name); | |
| if (!_spine_skeleton_data->findAnimation(new_animation_state.c_str())) { | |
| CLOG_ERROR(L"animation: {} does not exist", state_animation._state_name); | |
| // reset external component info. | |
| state_animation._state_name = data.animation_state_name; | |
| return false; | |
| } | |
| // update internal info. | |
| data.animation_state_name = state_animation._state_name; | |
| // update animation to new. | |
| /*if (initial_sync) {*/ | |
| if (true) { | |
| _spine_animation_state->setAnimation(0, new_animation_state.c_str(), state_animation._repeat); | |
| } | |
| else { | |
| _spine_animation_state->addAnimation(0, new_animation_state.c_str(), state_animation._repeat, | |
| _spine_animation_state_data->getDefaultMix()); | |
| } | |
| // _spine_animation_state->addAnimation(0, new_animation_state.c_str(), true, 0); | |
| return true; | |
| } | |
| } | |
| void NSprite_Spine::Draw(const lvn::TimeUnit delta_ms) | |
| { | |
| auto& state = GetState(); | |
| if (!_buffer) { | |
| _buffer = (CVertexArrayBuffer::Create()); | |
| } | |
| const auto custom_shaders = state.GetComponent<CShaderCtrl>().GetSpriteShaders(); | |
| // read spine render commands | |
| { | |
| auto p_command = _skeleton_renderer.render(*_spine_skeleton); | |
| while (p_command) | |
| { | |
| auto& command = *p_command; | |
| const auto positions = command.positions; | |
| const auto uvs = command.uvs; | |
| const auto colors = command.colors; | |
| const auto indices = command.indices; | |
| std::vector<CVertexArrayBuffer::Vertex> data_vertices; | |
| { | |
| //for (int ii = 0; ii < (command.numVertices << 1); ii += 2) | |
| for (int i = 0, j = 0; i < (command.numVertices); i++, j += 2) | |
| { | |
| CVertexArrayBuffer::Vertex vertex; | |
| vertex.x = positions[j]; | |
| vertex.y = positions[j + 1]; | |
| vertex.u = uvs[j]; | |
| vertex.v = (1.f - uvs[j + 1]); | |
| // colour | |
| { | |
| const auto& colour = colors[i]; | |
| vertex.rgba.r = (colour >> 24) & 0xFF; | |
| vertex.rgba.g = (colour >> 16) & 0xFF; | |
| vertex.rgba.b = (colour >> 8) & 0xFF; | |
| vertex.rgba.a = (colour) & 0xFF; | |
| } | |
| data_vertices.emplace_back(vertex); | |
| } | |
| } | |
| std::vector<uint16_t> data_indices; | |
| { | |
| for (int ii = 0; ii < command.numIndices; ii++) { | |
| data_indices.emplace_back(indices[ii]); | |
| } | |
| } | |
| CVertexArrayBuffer::RenderConfiguration config; | |
| { | |
| config.texture = static_cast<lvn::Texture*>(command.texture); | |
| config.blend_mode = to_lvn_blendmode(command.blendMode); | |
| //state.GetComponent<CSpriteAnimationCtrl>(). | |
| } | |
| // render | |
| { | |
| _buffer->Render(*this, | |
| data_vertices, | |
| data_indices, | |
| config, | |
| custom_shaders | |
| ); | |
| } | |
| //engine_drawMesh(vertices.buffer(), command.indices, command.numIndices, texture, blendMode); | |
| p_command = command.next; | |
| } | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment