diff --git a/.gitmodules b/.gitmodules index 2d582f48..1a8a54f9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "external/beautiful-win32-api"] path = external/beautiful-win32-api url = https://github.com/Demonese/beautiful-win32-api +[submodule "external/spine-runtimes"] + path = external/spine-runtimes + url = https://github.com/EsotericSoftware/spine-runtimes.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 017347be..200ec7db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,12 +8,18 @@ option(LUASTG_LINK_YY_THUNKS "Link to YY_Thunks for older Windows version (not r option(LUASTG_LINK_LUASOCKET "Link to luasocket" OFF) option(LUASTG_LINK_TRACY_CLIENT "Link to Tracy client" OFF) option(LUASTG_LINK_STEAM_API "Link to Steam API" OFF) +option(LUASTG_LINK_SPINE_RUNTIME "Link to Spine Runtime" OFF) if(LUASTG_SUPPORTS_WINDOWS_7) message(STATUS "[LuaSTG] Windows compatibility: Windows 7") add_compile_definitions(LUASTG_SUPPORTS_WINDOWS_7) endif() +if(LUASTG_LINK_SPINE_RUNTIME) + message(STATUS "[LuaSTG] Spine support: ON") + add_compile_definitions(LUASTG_SUPPORTS_SPINE) +endif() + set_property(GLOBAL PROPERTY USE_FOLDERS ON) include(cmake/TargetCommonOptions.cmake) include(cmake/options.cmake) diff --git a/LuaSTG/CMakeLists.txt b/LuaSTG/CMakeLists.txt index d8af4518..08ad58ba 100644 --- a/LuaSTG/CMakeLists.txt +++ b/LuaSTG/CMakeLists.txt @@ -45,6 +45,10 @@ set(LUASTG_ENGINE_SOURCES LuaSTG/GameResource/ResourceBase.hpp LuaSTG/GameResource/ResourceTexture.hpp LuaSTG/GameResource/ResourceSprite.hpp + LuaSTG/GameResource/ResourceSpineAtlas.hpp + LuaSTG/GameResource/ResourceSpineSkeleton.hpp + LuaSTG/GameResource/ResourceSpineAdaptor.hpp + LuaSTG/GameResource/ResourceSpineAdaptor.cpp LuaSTG/GameResource/ResourceAnimation.hpp LuaSTG/GameResource/ResourceMusic.hpp LuaSTG/GameResource/ResourceSoundEffect.hpp @@ -66,6 +70,10 @@ set(LUASTG_ENGINE_SOURCES LuaSTG/GameResource/Implement/ResourceTextureImpl.cpp LuaSTG/GameResource/Implement/ResourceSpriteImpl.hpp LuaSTG/GameResource/Implement/ResourceSpriteImpl.cpp + LuaSTG/GameResource/Implement/ResourceSpineAtlasImpl.hpp + LuaSTG/GameResource/Implement/ResourceSpineAtlasImpl.cpp + LuaSTG/GameResource/Implement/ResourceSpineSkeletonImpl.hpp + LuaSTG/GameResource/Implement/ResourceSpineSkeletonImpl.cpp LuaSTG/GameResource/Implement/ResourceAnimationImpl.hpp LuaSTG/GameResource/Implement/ResourceAnimationImpl.cpp LuaSTG/GameResource/Implement/ResourceMusicImpl.hpp @@ -98,6 +106,7 @@ set(LUASTG_ENGINE_SOURCES LuaSTG/LuaBinding/LW_ParticleSystem.cpp LuaSTG/LuaBinding/LW_Render.cpp LuaSTG/LuaBinding/LW_Renderer.cpp + LuaSTG/LuaBinding/LW_Spine.cpp LuaSTG/LuaBinding/LW_StopWatch.cpp LuaSTG/LuaBinding/LW_ResourceMgr.cpp LuaSTG/LuaBinding/LW_Platform.cpp diff --git a/LuaSTG/Core.cmake b/LuaSTG/Core.cmake index 2167d58c..1751eebf 100644 --- a/LuaSTG/Core.cmake +++ b/LuaSTG/Core.cmake @@ -126,3 +126,9 @@ target_link_libraries(Core PUBLIC Core.FileSystem win32 ) + +if (LUASTG_LINK_SPINE_RUNTIME) + target_link_libraries(Core PUBLIC spine-cpp) + #target_compile_definitions(Core PUBLIC LUASTG_SUPPORTS_SPINE) + message(STATUS "[Core] Link: spine-runtime") +endif () \ No newline at end of file diff --git a/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineAtlasImpl.cpp b/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineAtlasImpl.cpp new file mode 100644 index 00000000..56359328 --- /dev/null +++ b/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineAtlasImpl.cpp @@ -0,0 +1,19 @@ +#ifdef LUASTG_SUPPORTS_SPINE +#include "GameResource/Implement/ResourceSpineAtlasImpl.hpp" +#include "GameResource/Implement/ResourceTextureImpl.hpp" +#include "AppFrame.h" +#include "Core/FileSystem.hpp" +#include "Core/SmartReference.hpp" + +namespace luastg +{ + const std::shared_ptr& ResourceSpineAtlasImpl::getAtlas() { return atlas; } + ResourceSpineAtlasImpl::ResourceSpineAtlasImpl(const char* name, const char* atlas_path, spine::TextureLoader* textureLoader) + : ResourceBaseImpl(ResourceType::SpineAtlas, name), + atlas(new spine::Atlas(atlas_path, textureLoader)) + { + + } + +} +#endif // LUASTG_SUPPORTS_SPINE \ No newline at end of file diff --git a/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineAtlasImpl.hpp b/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineAtlasImpl.hpp new file mode 100644 index 00000000..01680e81 --- /dev/null +++ b/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineAtlasImpl.hpp @@ -0,0 +1,20 @@ +#pragma once +#ifdef LUASTG_SUPPORTS_SPINE +#include "GameResource/ResourceSpineAtlas.hpp" +#include "GameResource/Implement/ResourceBaseImpl.hpp" +#include + +namespace luastg +{ + class ResourceSpineAtlasImpl : public ResourceBaseImpl + { + private: + std::shared_ptr atlas; + + public: + const std::shared_ptr& getAtlas(); + public: + ResourceSpineAtlasImpl(const char* name, const char* atlasPath, spine::TextureLoader* textureLoader); + }; +} +#endif // LUASTG_SUPPORTS_SPINE \ No newline at end of file diff --git a/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineSkeletonImpl.cpp b/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineSkeletonImpl.cpp new file mode 100644 index 00000000..32971bc4 --- /dev/null +++ b/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineSkeletonImpl.cpp @@ -0,0 +1,50 @@ +#ifdef LUASTG_SUPPORTS_SPINE +#include "GameResource/Implement/ResourceSpineSkeletonImpl.hpp" +#include "AppFrame.h" + + +namespace luastg +{ + ResourceSpineSkeletonImpl::ResourceSpineSkeletonImpl(const char* name, const char* skel_path, const std::shared_ptr& atlas) + : ResourceBaseImpl(ResourceType::SpineSkeleton, name) + , atlas_holder(atlas) + { + std::string_view skeleton_path = skel_path; + if (skeleton_path.ends_with(".json")) + { + auto skelJson = spine::SkeletonJson(atlas.get()); + skeleton.reset(skelJson.readSkeletonDataFile(skel_path)); + } + else if (skeleton_path.ends_with(".skel")) + { + auto skelBin = spine::SkeletonBinary(atlas.get()); + skeleton.reset(skelBin.readSkeletonDataFile(skel_path)); + } + else + { + spdlog::error("'{}' 不是可识别的SpineSkeleton文件.", skel_path); + throw std::exception("spine skeleton only support .json and .skel format, check your skeleton file!"); + } + + if (skeleton.get() == nullptr) throw std::exception("load spine skeleton failed! check your spine export version and make sure it's 4.2.xx!"); + + anistate.reset(new spine::AnimationStateData(skeleton.get())); + } + spine::SkeletonData* ResourceSpineSkeletonImpl::getSkeletonData() { return skeleton.get(); } + spine::AnimationStateData* ResourceSpineSkeletonImpl::getAnimationStateData() { return anistate.get(); } + void ResourceSpineSkeletonImpl::setAnimationMix(const char* ani1, const char* ani2, float mix_time) + { + auto from = skeleton->findAnimation(ani1); + auto to = skeleton->findAnimation(ani2); + + if (!from || !to) + { + spdlog::error("SetSpineAnimationMix: 指定的骨骼 '{}' 上不存在动画 '{}' 和/或 '{}', 已取消", GetResName(), ani1, ani2); + return; + } + + anistate->setMix(ani1, ani2, mix_time); + } + void ResourceSpineSkeletonImpl::setAnimationMix(float mix_time) { anistate->setDefaultMix(mix_time); }; +} +#endif \ No newline at end of file diff --git a/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineSkeletonImpl.hpp b/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineSkeletonImpl.hpp new file mode 100644 index 00000000..40ebed7e --- /dev/null +++ b/LuaSTG/LuaSTG/GameResource/Implement/ResourceSpineSkeletonImpl.hpp @@ -0,0 +1,25 @@ +#pragma once +#ifdef LUASTG_SUPPORTS_SPINE +#include "GameResource/ResourceSpineSkeleton.hpp" +#include "GameResource/Implement/ResourceBaseImpl.hpp" +#include + +namespace luastg +{ + class ResourceSpineSkeletonImpl : public ResourceBaseImpl + { + private: + std::shared_ptr atlas_holder; + std::unique_ptr skeleton; + std::unique_ptr anistate; + + public: + spine::SkeletonData* getSkeletonData(); + spine::AnimationStateData* getAnimationStateData(); + void setAnimationMix(const char* ani1, const char* ani2, float mix_time); + void setAnimationMix(float mix_time); + public: + ResourceSpineSkeletonImpl(const char* name, const char* skelPath, const std::shared_ptr& atlas); + }; +} +#endif // LUASTG_SUPPORTS_SPINE \ No newline at end of file diff --git a/LuaSTG/LuaSTG/GameResource/ResourceBase.hpp b/LuaSTG/LuaSTG/GameResource/ResourceBase.hpp index 24922e86..7ee33132 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceBase.hpp +++ b/LuaSTG/LuaSTG/GameResource/ResourceBase.hpp @@ -14,6 +14,8 @@ namespace luastg { TrueTypeFont, FX, Model, + SpineAtlas, + SpineSkeleton, }; // 混合模式 diff --git a/LuaSTG/LuaSTG/GameResource/ResourceManager.cpp b/LuaSTG/LuaSTG/GameResource/ResourceManager.cpp index 42bf7340..587cbb47 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceManager.cpp +++ b/LuaSTG/LuaSTG/GameResource/ResourceManager.cpp @@ -114,6 +114,24 @@ namespace luastg return tRet; } +#ifdef LUASTG_SUPPORTS_SPINE + core::SmartReference ResourceMgr::FindSpineAtlas(const char* name) noexcept + { + core::SmartReference tRet; + if (!(tRet = m_StageResourcePool.GetSpineAtlas(name))) + tRet = m_GlobalResourcePool.GetSpineAtlas(name); + return tRet; + } + + core::SmartReference ResourceMgr::FindSpineSkeleton(const char* name) noexcept + { + core::SmartReference tRet; + if (!(tRet = m_StageResourcePool.GetSpineSkeleton(name))) + tRet = m_GlobalResourcePool.GetSpineSkeleton(name); + return tRet; + } +#endif // LUASTG_SUPPORTS_SPINE + // 其他资源操作 bool ResourceMgr::GetTextureSize(const char* name, core::Vector2U& out) noexcept { diff --git a/LuaSTG/LuaSTG/GameResource/ResourceManager.h b/LuaSTG/LuaSTG/GameResource/ResourceManager.h index 431926cd..82129195 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceManager.h +++ b/LuaSTG/LuaSTG/GameResource/ResourceManager.h @@ -9,6 +9,9 @@ #include "GameResource/ResourceFont.hpp" #include "GameResource/ResourcePostEffectShader.hpp" #include "GameResource/ResourceModel.hpp" +#include "GameResource/ResourceSpineAdaptor.hpp" +#include "GameResource/ResourceSpineAtlas.hpp" +#include "GameResource/ResourceSpineSkeleton.hpp" #include "lua.hpp" #include "xxhash.h" @@ -87,6 +90,10 @@ namespace luastg dictionary_t> m_TTFFontPool; dictionary_t> m_FXPool; dictionary_t> m_ModelPool; +#ifdef LUASTG_SUPPORTS_SPINE + dictionary_t> m_SpineAtlasPool; + dictionary_t> m_SpineSkeletonPool; +#endif // LUASTG_SUPPORTS_SPINE private: const char* getResourcePoolTypeName(); public: @@ -132,7 +139,12 @@ namespace luastg bool LoadFX(const char* name, const char* path) noexcept; // 模型 bool LoadModel(const char* name, const char* path) noexcept; - + // Spine +#ifdef LUASTG_SUPPORTS_SPINE + bool LoadSpineAtlas(const char* name, const char* atlas_path) noexcept; + bool LoadSpineSkeleton(const char* name, const char* atlas_name, const char* skeleton_path) noexcept; +#endif // LUASTG_SUPPORTS_SPINE + core::SmartReference GetTexture(std::string_view name) noexcept; core::SmartReference GetSprite(std::string_view name) noexcept; core::SmartReference GetAnimation(std::string_view name) noexcept; @@ -143,6 +155,10 @@ namespace luastg core::SmartReference GetTTFFont(std::string_view name) noexcept; core::SmartReference GetFX(std::string_view name) noexcept; core::SmartReference GetModel(std::string_view name) noexcept; +#ifdef LUASTG_SUPPORTS_SPINE + core::SmartReference GetSpineAtlas(std::string_view name) noexcept; + core::SmartReference GetSpineSkeleton(std::string_view name) noexcept; +#endif // LUASTG_SUPPORTS_SPINE public: ResourcePool(ResourceMgr* mgr, ResourcePoolType t); ResourcePool& operator=(const ResourcePool&) = delete; @@ -173,7 +189,11 @@ namespace luastg core::SmartReference FindTTFFont(const char* name) noexcept; core::SmartReference FindFX(const char* name) noexcept; core::SmartReference FindModel(const char* name) noexcept; - +#ifdef LUASTG_SUPPORTS_SPINE + core::SmartReference FindSpineAtlas(const char* name) noexcept; + core::SmartReference FindSpineSkeleton(const char* name) noexcept; +#endif + bool GetTextureSize(const char* name, core::Vector2U& out) noexcept; void CacheTTFFontString(const char* name, const char* text, size_t len) noexcept; void UpdateSound(); diff --git a/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp b/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp index 76fa8666..cafc1654 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp +++ b/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp @@ -8,6 +8,8 @@ #include "GameResource/Implement/ResourceFontImpl.hpp" #include "GameResource/Implement/ResourcePostEffectShaderImpl.hpp" #include "GameResource/Implement/ResourceModelImpl.hpp" +#include "GameResource/Implement/ResourceSpineAtlasImpl.hpp" +#include "GameResource/Implement/ResourceSpineSkeletonImpl.hpp" #include "core/FileSystem.hpp" #include "AppFrame.h" #include "lua/plus.hpp" @@ -28,6 +30,10 @@ namespace luastg m_TTFFontPool.clear(); m_FXPool.clear(); m_ModelPool.clear(); +#ifdef LUASTG_SUPPORTS_SPINE + m_SpineAtlasPool.clear(); + m_SpineSkeletonPool.clear(); +#endif // LUASTG_SUPPORTS_SPINE spdlog::info("[luastg] 已清空资源池 '{}'", getResourcePoolTypeName()); } @@ -93,6 +99,14 @@ namespace luastg case ResourceType::Model: removeResource(m_ModelPool, name); break; +#ifdef LUASTG_SUPPORTS_SPINE + case ResourceType::SpineAtlas: + removeResource(m_SpineAtlasPool, name); + break; + case ResourceType::SpineSkeleton: + removeResource(m_SpineSkeletonPool, name); + break; +#endif default: spdlog::warn("[luastg] RemoveResource: 试图移除一个不存在的资源类型 ({})", (int)t); return; @@ -123,6 +137,12 @@ namespace luastg return m_FXPool.find(name) != m_FXPool.end(); case ResourceType::Model: return m_ModelPool.find(name) != m_ModelPool.end(); +#ifdef LUASTG_SUPPORTS_SPINE + case ResourceType::SpineAtlas: + return m_SpineAtlasPool.find(name) != m_SpineAtlasPool.end(); + case ResourceType::SpineSkeleton: + return m_SpineSkeletonPool.find(name) != m_SpineSkeletonPool.end(); +#endif default: spdlog::warn("[luastg] CheckRes: 试图检索一个不存在的资源类型({})", (int)t); break; @@ -179,6 +199,14 @@ namespace luastg case ResourceType::Model: listResourceName(L, m_ModelPool); break; +#ifdef LUASTG_SUPPORTS_SPINE + case ResourceType::SpineAtlas: + listResourceName(L, m_SpineAtlasPool); + break; + case ResourceType::SpineSkeleton: + listResourceName(L, m_SpineSkeletonPool); + break; +#endif default: spdlog::warn("[luastg] EnumRes: 试图枚举一个不存在的资源类型({})", (int)t); S.create_array(0); @@ -868,7 +896,7 @@ namespace luastg } return true; } - + try { core::SmartReference tRes; @@ -880,15 +908,90 @@ namespace luastg spdlog::error("[luastg] LoadModel: 无法加载模型 '{}' ({})", name, e.what()); return false; } - + if (ResourceMgr::GetResourceLoadingLog()) { spdlog::info("[luastg] LoadModel: 已从 '{}' 加载模型 '{}' ({})", path, name, getResourcePoolTypeName()); } - + + return true; + } + +#ifdef LUASTG_SUPPORTS_SPINE + // 加载SpineAtlas + + bool ResourcePool::LoadSpineAtlas(const char* name, const char* atlas_path) noexcept + { + if (m_SpineAtlasPool.find(std::string_view(name)) != m_SpineAtlasPool.end()) + { + if (ResourceMgr::GetResourceLoadingLog()) + { + spdlog::warn("[luastg] LoadSpineAtlas: SpineAtlas '{}' 已存在,加载操作已取消", name); + } + return true; + } + + try + { + core::SmartReference tRes; + tRes.attach(new ResourceSpineAtlasImpl(name, atlas_path, &spine::LuaSTGTextureLoader::Instance())); + m_SpineAtlasPool.emplace(name, tRes); + } + catch (std::exception const& e) + { + spdlog::error("[luastg] LoadSpineAtlas: 无法加载SpineAtlas '{}' ({})", name, e.what()); + return false; + } + + if (ResourceMgr::GetResourceLoadingLog()) + { + spdlog::info("[luastg] LoadSpineAtlas: 已从 '{}' 加载SpineAtlas '{}' ({})", atlas_path, name, getResourcePoolTypeName()); + } + return true; } + // 加载SpineSkeleton + + bool ResourcePool::LoadSpineSkeleton(const char* name, const char* atlas_name, const char* skeleton_path) noexcept + { + if (m_SpineSkeletonPool.find(std::string_view(name)) != m_SpineSkeletonPool.end()) + { + if (ResourceMgr::GetResourceLoadingLog()) + { + spdlog::warn("[luastg] LoadSpineSkeleton: SpineSkeleton '{}' 已存在,加载操作已取消", name); + } + return true; + } + + core::SmartReference pAtlas = m_pMgr->FindSpineAtlas(atlas_name); + if (!pAtlas) + { + spdlog::error("[luastg] LoadSpineSkeleton: 无法创建SpineSkeleton '{}',因为无法找到SpineAtlas '{}'", name, atlas_name); + return false; + } + + try + { + core::SmartReference tRes; + tRes.attach(new ResourceSpineSkeletonImpl(name, skeleton_path, pAtlas->getAtlas())); + m_SpineSkeletonPool.emplace(name, tRes); + } + catch (std::exception const& e) + { + spdlog::error("[luastg] LoadSpineSkeleton: 无法加载SpineSkeleton '{}' ({})", name, e.what()); + return false; + } + + if (ResourceMgr::GetResourceLoadingLog()) + { + spdlog::info("[luastg] LoadSpineSkeleton: 已从 '{}' 加载SpineSkeleton '{}' ({})", skeleton_path, name, getResourcePoolTypeName()); + } + + return true; + } +#endif + // 查找并获取 template @@ -946,10 +1049,22 @@ namespace luastg return findResource(m_FXPool, name); } - core::SmartReference ResourcePool::GetModel(std::string_view name) noexcept - { + core::SmartReference ResourcePool::GetModel(std::string_view name) noexcept + { return findResource(m_ModelPool, name); - } + } + +#ifdef LUASTG_SUPPORTS_SPINE + core::SmartReference ResourcePool::GetSpineAtlas(std::string_view name) noexcept + { + return findResource(m_SpineAtlasPool, name); + } + + core::SmartReference ResourcePool::GetSpineSkeleton(std::string_view name) noexcept + { + return findResource(m_SpineSkeletonPool, name); + } +#endif // LUASTG_SUPPORTS_SPINE ResourcePool::ResourcePool(ResourceMgr* mgr, ResourcePoolType t) : m_pMgr(mgr) @@ -964,6 +1079,10 @@ namespace luastg , m_TTFFontPool(&m_memory_resource) , m_FXPool(&m_memory_resource) , m_ModelPool(&m_memory_resource) +#ifdef LUASTG_SUPPORTS_SPINE + , m_SpineAtlasPool(&m_memory_resource) + , m_SpineSkeletonPool(&m_memory_resource) +#endif // LUASTG_SUPPORTS_SPINE { } diff --git a/LuaSTG/LuaSTG/GameResource/ResourceSpineAdaptor.cpp b/LuaSTG/LuaSTG/GameResource/ResourceSpineAdaptor.cpp new file mode 100644 index 00000000..d7a7c283 --- /dev/null +++ b/LuaSTG/LuaSTG/GameResource/ResourceSpineAdaptor.cpp @@ -0,0 +1,108 @@ +#ifdef LUASTG_SUPPORTS_SPINE +#include "ResourceSpineAdaptor.hpp" +#include "AppFrame.h" +#include "Core/FileSystem.hpp" + +namespace spine +{ + using SamplerStateEnum = core::Graphics::IRenderer::SamplerState; + // 沟槽的采样模式映射 + static SamplerStateEnum mapSpineToD3D11( + TextureFilter minFilter, TextureFilter magFilter, + TextureWrap uWrap, TextureWrap vWrap, + bool& enableMipmap) + { + bool isLinear = false; + if (minFilter == TextureFilter_Linear || minFilter == TextureFilter_MipMapLinearNearest || + minFilter == TextureFilter_MipMap || magFilter == TextureFilter_Linear) + isLinear = true; + + if (minFilter == TextureFilter_MipMapNearestNearest || magFilter == TextureFilter_MipMapNearestNearest || + minFilter == TextureFilter_MipMapLinearNearest || magFilter == TextureFilter_MipMapLinearNearest || + minFilter == TextureFilter_MipMapNearestLinear || magFilter == TextureFilter_MipMapNearestLinear || + minFilter == TextureFilter_MipMapLinearLinear || magFilter == TextureFilter_MipMapLinearLinear) + enableMipmap = true; + + SamplerStateEnum mode; + if (uWrap == TextureWrap_Repeat) + mode = isLinear ? SamplerStateEnum::LinearWrap : SamplerStateEnum::PointWrap; + else if (uWrap == TextureWrap_ClampToEdge) + mode = isLinear ? SamplerStateEnum::LinearClamp : SamplerStateEnum::PointClamp; + else + mode = SamplerStateEnum::LinearWrap; + + return mode; + } + + void LuaSTGdummyOnAnimationEventFunc(AnimationState* state, spine::EventType type, TrackEntry* entry, Event* event) { + SP_UNUSED(state); + SP_UNUSED(type); + SP_UNUSED(entry); + SP_UNUSED(event); + } + + LuaSTGAtlasAttachmentLoader::LuaSTGAtlasAttachmentLoader(Atlas* atlas) : AtlasAttachmentLoader(atlas) {} + LuaSTGAtlasAttachmentLoader::~LuaSTGAtlasAttachmentLoader() {} + void LuaSTGAtlasAttachmentLoader::configureAttachment(Attachment* attachment) {} + + LuaSTGTextureLoader::LuaSTGTextureLoader() : TextureLoader() {} + LuaSTGTextureLoader::~LuaSTGTextureLoader() {} + void LuaSTGTextureLoader::load(AtlasPage& page, const spine::String& path) + { + bool enableMipmap = false; + SamplerStateEnum state = mapSpineToD3D11(page.minFilter, page.magFilter, page.uWrap, page.uWrap, enableMipmap); + + core::Graphics::ITexture2D* p_texture; + if (!LAPP.GetAppModel()->getDevice()->createTextureFromFile(path.buffer(), enableMipmap, &p_texture)) + { + spdlog::error("[luastg] 从 '{}' 创建Spine纹理失败", path.buffer()); + throw std::exception("failed loading spine atlas's texture, please make sure file structure is as same as it was exported!"); + return; + } + core::Graphics::ISamplerState* p_sampler = LAPP.GetRenderer2D()->getKnownSamplerState(state); + p_texture->setSamplerState(p_sampler); + + auto [w, h] = p_texture->getSize(); + + page.texture = p_texture; + page.width = w; + page.height = h; + } + void LuaSTGTextureLoader::unload(void* texture) + { + if (!texture) return; + core::Graphics::ITexture2D* p_texture = (core::Graphics::ITexture2D*)texture; + p_texture->release(); + } + LuaSTGTextureLoader& LuaSTGTextureLoader::Instance() + { + static LuaSTGTextureLoader _instance; + return _instance; + } + + LuaSTGExtension::LuaSTGExtension() : DefaultSpineExtension() {} + LuaSTGExtension::~LuaSTGExtension() {} + char* LuaSTGExtension::_readFile(const spine::String& path, int* length) { + core::IData* data; + if(!core::FileSystemManager::readFile(path.buffer(), &data)) return nullptr; + + *length = static_cast(data->size()); + char* bytes = SpineExtension::alloc(*length, __FILE__, __LINE__); + std::memcpy(bytes, data->data(), *length); + data->release(); + return bytes; + } + LuaSTGExtension& LuaSTGExtension::Instance() { + static LuaSTGExtension _instance; + return _instance; + } + + SkeletonRenderer& LuaSTGSkeletonRenderer::Instance() + { + static SkeletonRenderer _instance; + return _instance; + }; + + SpineExtension* spine::getDefaultExtension() { return &LuaSTGExtension::Instance(); } +} +#endif \ No newline at end of file diff --git a/LuaSTG/LuaSTG/GameResource/ResourceSpineAdaptor.hpp b/LuaSTG/LuaSTG/GameResource/ResourceSpineAdaptor.hpp new file mode 100644 index 00000000..151e7b1b --- /dev/null +++ b/LuaSTG/LuaSTG/GameResource/ResourceSpineAdaptor.hpp @@ -0,0 +1,46 @@ +#pragma once +#ifdef LUASTG_SUPPORTS_SPINE +#include + +namespace spine +{ + void LuaSTGdummyOnAnimationEventFunc(AnimationState* state, spine::EventType type, TrackEntry* entry, Event* event); + + class LuaSTGAtlasAttachmentLoader : public AtlasAttachmentLoader + { + public: + LuaSTGAtlasAttachmentLoader(Atlas *atlas); + virtual ~LuaSTGAtlasAttachmentLoader(); + virtual void configureAttachment(Attachment *attachment); + }; + + class LuaSTGTextureLoader : public TextureLoader + { + public: + LuaSTGTextureLoader(); + virtual ~LuaSTGTextureLoader(); + virtual void load(AtlasPage &page, const String &path); + virtual void unload(void *texture); + static LuaSTGTextureLoader& Instance(); + }; + + class LuaSTGExtension : public DefaultSpineExtension + { + private: + public: + static LuaSTGExtension& Instance(); + LuaSTGExtension(); + virtual ~LuaSTGExtension(); + + protected: + virtual char *_readFile(const String &path, int *length); + }; + + class LuaSTGSkeletonRenderer + { + public: + static SkeletonRenderer& Instance(); + }; +} + +#endif \ No newline at end of file diff --git a/LuaSTG/LuaSTG/GameResource/ResourceSpineAtlas.hpp b/LuaSTG/LuaSTG/GameResource/ResourceSpineAtlas.hpp new file mode 100644 index 00000000..31e04608 --- /dev/null +++ b/LuaSTG/LuaSTG/GameResource/ResourceSpineAtlas.hpp @@ -0,0 +1,22 @@ +#pragma once +#ifdef LUASTG_SUPPORTS_SPINE + +#include "GameResource/ResourceBase.hpp" +#include + +namespace luastg +{ + struct IResourceSpineAtlas : public IResourceBase + { + virtual const std::shared_ptr& getAtlas() = 0; + }; +} + +namespace core { + // UUID v5 + // ns:URL + // https://www.luastg-sub.com/luastg.IResourceSpineAtlas + template<> constexpr InterfaceId getInterfaceId() { return UUID::parse("a7995836-0ed4-57ee-a014-417162b8541d"); } +} + +#endif // LUASTG_SUPPORTS_SPINE \ No newline at end of file diff --git a/LuaSTG/LuaSTG/GameResource/ResourceSpineSkeleton.hpp b/LuaSTG/LuaSTG/GameResource/ResourceSpineSkeleton.hpp new file mode 100644 index 00000000..789a9901 --- /dev/null +++ b/LuaSTG/LuaSTG/GameResource/ResourceSpineSkeleton.hpp @@ -0,0 +1,25 @@ +#pragma once +#ifdef LUASTG_SUPPORTS_SPINE + +#include "GameResource/ResourceBase.hpp" +#include + +namespace luastg +{ + struct IResourceSpineSkeleton : public IResourceBase + { + virtual spine::SkeletonData* getSkeletonData() = 0; + virtual spine::AnimationStateData* getAnimationStateData() = 0; + virtual void setAnimationMix(const char* ani1, const char* ani2, float mix_time) = 0; + virtual void setAnimationMix(float mix_time) = 0; + }; +} + +namespace core { + // UUID v5 + // ns:URL + // https://www.luastg-sub.com/luastg.IResourceSpineSkeleton + template<> constexpr InterfaceId getInterfaceId() { return UUID::parse("7ff21330-a4cc-580b-bd25-adb5bbb09c90"); } +} + +#endif // LUASTG_SUPPORTS_SPINE \ No newline at end of file diff --git a/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp b/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp index 3557daf9..35964033 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp +++ b/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp @@ -351,7 +351,7 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { const char* name = luaL_checkstring(L, 1); const char* model_path = luaL_checkstring(L, 2); - + ResourcePool* pActivedPool = LRES.GetActivedPool(); if (!pActivedPool) return luaL_error(L, "can't load resource at this time."); @@ -363,6 +363,48 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept } return 0; } + static int LoadSpineAtlas(lua_State* L) noexcept + { +#ifdef LUASTG_SUPPORTS_SPINE + const char* name = luaL_checkstring(L, 1); + const char* atlas_path = luaL_checkstring(L, 2); + + ResourcePool* pActivedPool = LRES.GetActivedPool(); + if (!pActivedPool) + return luaL_error(L, "can't load resource at this time."); + if (!pActivedPool->LoadSpineAtlas( + name, + atlas_path)) + { + return luaL_error(L, "load spineAtlas failed (name='%s', atlas='%s').", name, atlas_path); + } + return 0; +#else + return luaL_error(L, "spine is not supported in this version!"); +#endif + } + static int LoadSpineSkeleton(lua_State* L) noexcept + { +#ifdef LUASTG_SUPPORTS_SPINE + const char* name = luaL_checkstring(L, 1); + const char* atlas_name = luaL_checkstring(L, 2); + const char* skel_path = luaL_checkstring(L, 3); + + ResourcePool* pActivedPool = LRES.GetActivedPool(); + if (!pActivedPool) + return luaL_error(L, "can't load resource at this time."); + if (!pActivedPool->LoadSpineSkeleton( + name, + atlas_name, + skel_path)) + { + return luaL_error(L, "load spineSkeleton failed (name='%s', atlas='%s', skeleton='%s').", name, atlas_name, skel_path); + } + return 0; +#else + return luaL_error(L, "spine is not supported in this version!"); +#endif + } static int CreateRenderTarget(lua_State* L) noexcept { const char* name = luaL_checkstring(L, 1); @@ -665,6 +707,30 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept return 0; } + static int SetSpineAnimationMix(lua_State* L) + { +#ifdef LUASTG_SUPPORTS_SPINE + + const char* name = luaL_checkstring(L, 1); + core::SmartReference p = LRES.FindSpineSkeleton(name); + if (!p) return luaL_error(L, "spine skeleton '%s' not found.", name); + + if (lua_gettop(L) == 2) + { + p->setAnimationMix(luaL_checknumber(L, 2)); + return 0; + } + + const char* ani1 = luaL_checkstring(L, 2); + const char* ani2 = luaL_checkstring(L, 3); + p->setAnimationMix(ani1, ani2, luaL_checknumber(L, 4)); + + return 0; +#else + return luaL_error(L, "spine is not supported in this version!"); +#endif + } + static int CacheTTFString(lua_State* L) { size_t len = 0; const char* str = luaL_checklstring(L, 2, &len); @@ -688,6 +754,8 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { "LoadTrueTypeFont", &Wrapper::LoadTrueTypeFont }, { "LoadFX", &Wrapper::LoadFX }, { "LoadModel", &Wrapper::LoadModel }, + { "LoadSpineAtlas", &Wrapper::LoadSpineAtlas }, + { "LoadSpineSkeleton", &Wrapper::LoadSpineSkeleton }, { "CreateRenderTarget", &Wrapper::CreateRenderTarget }, { "IsRenderTarget", &Wrapper::IsRenderTarget }, { "SetTexturePreMulAlphaState", &Wrapper::SetTexturePreMulAlphaState }, @@ -709,6 +777,8 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { "SetFontState", &Wrapper::SetFontState }, + { "SetSpineAnimationMix", &Wrapper::SetSpineAnimationMix }, + { "CacheTTFString", &Wrapper::CacheTTFString }, { NULL, NULL }, }; diff --git a/LuaSTG/LuaSTG/LuaBinding/LW_Spine.cpp b/LuaSTG/LuaSTG/LuaBinding/LW_Spine.cpp new file mode 100644 index 00000000..2dffc207 --- /dev/null +++ b/LuaSTG/LuaSTG/LuaBinding/LW_Spine.cpp @@ -0,0 +1,818 @@ +#include "LuaBinding/LuaWrapper.hpp" +#ifdef LUASTG_SUPPORTS_SPINE +#include + +namespace spine +{ + struct LuaSTGSlotCacheHelper + { + Slot* slot; + int index; + }; + + class LuaSTGSpineInstance + { + private: + std::string_view resname; + std::unique_ptr skeleton; + std::unique_ptr anistate; + std::unordered_map bonecache; + std::unordered_map animationcache; + std::unordered_map skincache; + std::unordered_map slotcache; + int animation_callback = LUA_NOREF; + int event_callback = LUA_NOREF; + int self_weak_table = LUA_NOREF; + public: + const std::string_view& getName(); + const std::unordered_map& getAllBones(); + const std::unordered_map& getAllAnimations(); + const std::unordered_map& getAllSkins(); + const std::unordered_map & getAllSlots(); + Skeleton* getSkeleton(); + AnimationState* getAnimationState(); + Bone* findBone(const char* name); + Animation* findAnimation(const char* name); + Skin* findSkin(const char* name); + const LuaSTGSlotCacheHelper& findSlot(const char* name); + void setAnimationCallback(lua_State* L, int position); + void setEventCallback(lua_State* L, int position); + bool pushAnimationCallback(lua_State* L); + bool pushEventCallback(lua_State* L); + void makeSelfWeakTable(lua_State* L, int self_position); + bool pushSelf(lua_State* L); + public: + LuaSTGSpineInstance(const std::string_view& name, spine::SkeletonData* skeldata, spine::AnimationStateData* anidata); + ~LuaSTGSpineInstance(); + }; + + LuaSTGSpineInstance::LuaSTGSpineInstance(const std::string_view& name, spine::SkeletonData* skeldata, spine::AnimationStateData* anidata) + : resname(name) + , skeleton(new spine::Skeleton(skeldata)) + , anistate(new spine::AnimationState(anidata)) + { + // name -> bone mapping + const auto& bones = skeleton->getBones(); + const auto bone_size = bones.size(); + for (int i = 0; i < bone_size; i++) bonecache[bones[i]->getData().getName().buffer()] = bones[i]; + // name -> ani mapping + const auto& animations = skeldata->getAnimations(); + const auto ani_size = animations.size(); + for (int i = 0; i < ani_size; i++) animationcache[animations[i]->getName().buffer()] = animations[i]; + // name -> skin mapping + const auto& skins = skeldata->getSkins(); + const auto ski_size = skins.size(); + for (int i = 0; i < ski_size; i++) skincache[skins[i]->getName().buffer()] = skins[i]; + // name -> slot mapping + const auto& slots = skeleton->getSlots(); + const auto slt_size = slots.size(); + for (int i = 0; i < slt_size; i++) slotcache[slots[i]->getData().getName().buffer()] = {.slot = slots[i], .index = i }; + }; + const std::string_view& LuaSTGSpineInstance::getName() { return resname; } + const std::unordered_map& LuaSTGSpineInstance::getAllBones() { return bonecache; } + const std::unordered_map& LuaSTGSpineInstance::getAllAnimations() { return animationcache; } + const std::unordered_map& LuaSTGSpineInstance::getAllSkins() { return skincache; } + const std::unordered_map & LuaSTGSpineInstance::getAllSlots() { return slotcache; } + Skeleton* LuaSTGSpineInstance::getSkeleton() { return skeleton.get(); } + AnimationState* LuaSTGSpineInstance::getAnimationState() { return anistate.get(); } + Bone* LuaSTGSpineInstance::findBone(const char* name) { return bonecache.contains(name) ? bonecache[name] : nullptr; } + Animation* LuaSTGSpineInstance::findAnimation(const char* name) { return animationcache.contains(name) ? animationcache[name] : nullptr; } + Skin* LuaSTGSpineInstance::findSkin(const char* name) { return skincache.contains(name) ? skincache[name] : nullptr; } + const LuaSTGSlotCacheHelper& LuaSTGSpineInstance::findSlot(const char* name) { return slotcache.contains(name) ? slotcache[name] : LuaSTGSlotCacheHelper{ .slot = nullptr, .index = -1 }; } + void LuaSTGSpineInstance::setAnimationCallback(lua_State* L, int position) + { + if (animation_callback != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, animation_callback); + lua_pushvalue(L, position); + animation_callback = luaL_ref(L, LUA_REGISTRYINDEX); + }; + void LuaSTGSpineInstance::setEventCallback(lua_State* L, int position) + { + if (event_callback != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, event_callback); + lua_pushvalue(L, position); + event_callback = luaL_ref(L, LUA_REGISTRYINDEX); + }; + bool LuaSTGSpineInstance::pushAnimationCallback(lua_State* L) + { + if (animation_callback == LUA_NOREF) return false; + lua_rawgeti(L, LUA_REGISTRYINDEX, animation_callback); + return true; + } + bool LuaSTGSpineInstance::pushEventCallback(lua_State* L) + { + if (event_callback == LUA_NOREF) return false; + lua_rawgeti(L, LUA_REGISTRYINDEX, event_callback); + return true; + } + LuaSTGSpineInstance::~LuaSTGSpineInstance() + { + lua_State* L = LAPP.GetLuaEngine(); + if (animation_callback != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, animation_callback); + if (event_callback != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, event_callback); + if (self_weak_table != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, self_weak_table); + } + void LuaSTGSpineInstance::makeSelfWeakTable(lua_State* L, int self_position) + { + if (self_weak_table != LUA_NOREF) return; + + lua_newtable(L); // ..arg t + lua_newtable(L); // ..arg t mt + lua_pushstring(L, "v"); // ..arg t mt "v" + lua_setfield(L, -2, "__mode"); // ..arg t mt + lua_setmetatable(L, -2); // ..arg t + lua_pushvalue(L, self_position); // ..arg t self + lua_setfield(L, -2, "self"); // ..arg t + self_weak_table = luaL_ref(L, LUA_REGISTRYINDEX); // ..arg + } + bool LuaSTGSpineInstance::pushSelf(lua_State* L) + { + if (self_weak_table == LUA_NOREF) return false; + + lua_rawgeti(L, LUA_REGISTRYINDEX, self_weak_table); // ..arg t + lua_getfield(L, -1, "self"); // ..arg t self + lua_remove(L, -2); // ..arg self + + return true; + } +} + +using SpineInstance = spine::LuaSTGSpineInstance; + +namespace +{ + enum class Mapper + { + x, y, + vscale, hscale, + getExistBones, + getBoneInfo, setBoneState, + update, reset, render, + getExistAnimations, + addAnimation, setAnimation, + setAnimationTimeScale, getCurrentAnimation, + getExistEvents, setEventListener, + getExistSkins, setSkin, getCurrentSkin, + getExistSlots, setSlotAttachment, getCurrentAttachmentOnSlot, + }; + + const std::unordered_map KeyMapper + { + { "x", Mapper::x }, + { "y", Mapper::y }, + { "vscale", Mapper::vscale }, + { "hscale", Mapper::hscale }, + { "getExistBones", Mapper::getExistBones }, + { "getBoneInfo", Mapper::getBoneInfo }, + { "setBoneState", Mapper::setBoneState }, + { "update", Mapper::update }, + { "reset", Mapper::reset }, + { "render", Mapper::render }, + { "getExistAnimations", Mapper::getExistAnimations }, + { "addAnimation", Mapper::addAnimation }, + { "setAnimation", Mapper::setAnimation }, + { "setAnimationTimeScale", Mapper::setAnimationTimeScale }, + { "getCurrentAnimation", Mapper::getCurrentAnimation }, + { "getExistEvents", Mapper::getExistEvents }, + { "setEventListener", Mapper::setEventListener }, + { "getExistSkins", Mapper::getExistSkins }, + { "setSkin", Mapper::setSkin }, + { "getCurrentSkin", Mapper::getCurrentSkin }, + { "getExistSlots", Mapper::getExistSlots }, + { "setSlotAttachment", Mapper::setSlotAttachment }, + { "getCurrentAttachmentOnSlot", Mapper::getCurrentAttachmentOnSlot }, + + }; +} + +void luastg::binding::Spine::Register(lua_State* L) noexcept +{ +#define GETUDATA(p, i) SpineInstance* (p) = static_cast(luaL_checkudata(L, (i), LUASTG_LUA_TYPENAME_SPINE)); + struct Wrapper + { + static int CreateSpineInstance(lua_State* L) noexcept + { + const char* name = luaL_checkstring(L, 1); + + core::SmartReference pRes = LRES.FindSpineSkeleton(name); + if (!pRes) return luaL_error(L, "could not find spine skeleton '%s'.", name); + + luastg::binding::Spine::CreateAndPush(L, pRes.get()); + return 1; + } + static int CheckSpineSupport(lua_State* L) + { + lua_pushstring(L, "4.2"); + return 1; + } + static int __gc(lua_State* L) noexcept + { + GETUDATA(data, 1); + data->~SpineInstance(); + + return 0; + } + static int __index(lua_State* L) + { + GETUDATA(data, 1); + const char* key = luaL_checkstring(L, 2); + + if (!KeyMapper.contains(key)) { lua_pushnil(L); return 1; } + + switch (KeyMapper.at(key)) + { + case Mapper::x : + lua_pushnumber(L, data->getSkeleton()->getX()); break; + case Mapper::y : + lua_pushnumber(L, data->getSkeleton()->getY()); break; + case Mapper::vscale : + lua_pushnumber(L, data->getSkeleton()->getScaleX()); break; + case Mapper::hscale : + lua_pushnumber(L, data->getSkeleton()->getScaleY()); break; + case Mapper::getExistBones : + lua_pushcfunction(L, Wrapper::getExistBones); break; + case Mapper::getBoneInfo: + lua_pushcfunction(L, Wrapper::getBoneInfo); break; + case Mapper::setBoneState: + lua_pushcfunction(L, Wrapper::setBoneState); break; + case Mapper::update : + lua_pushcfunction(L, Wrapper::update); break; + case Mapper::reset : + lua_pushcfunction(L, Wrapper::reset); break; + case Mapper::render : + lua_pushcfunction(L, Wrapper::render); break; + case Mapper::getExistAnimations : + lua_pushcfunction(L, Wrapper::getExistAnimations); break; + case Mapper::addAnimation : + lua_pushcfunction(L, Wrapper::addAnimation); break; + case Mapper::setAnimation: + lua_pushcfunction(L, Wrapper::setAnimation); break; + case Mapper::getCurrentAnimation: + lua_pushcfunction(L, Wrapper::getCurrentAnimation); break; + case Mapper::getExistEvents: + lua_pushcfunction(L, Wrapper::getExistEvents); break; + case Mapper::setEventListener: + lua_pushcfunction(L, Wrapper::setEventListener); break; + case Mapper::getExistSkins: + lua_pushcfunction(L, Wrapper::getExistSkins); break; + case Mapper::setSkin: + lua_pushcfunction(L, Wrapper::setSkin); break; + case Mapper::getCurrentSkin: + lua_pushcfunction(L, Wrapper::getCurrentSkin); break; + case Mapper::getExistSlots: + lua_pushcfunction(L, Wrapper::getExistSlots); break; + case Mapper::setSlotAttachment: + lua_pushcfunction(L, Wrapper::setSlotAttachment); break; + case Mapper::getCurrentAttachmentOnSlot: + lua_pushcfunction(L, Wrapper::getCurrentAttachmentOnSlot); break; + + default : + lua_pushnil(L); break; + }; + + return 1; + } + static int __newindex(lua_State* L) + { + GETUDATA(data, 1); + const char* key = luaL_checkstring(L, 2); + + if (!KeyMapper.contains(key)) { lua_pushnil(L); return 1; } + + switch (KeyMapper.at(key)) + { + case Mapper::x: + data->getSkeleton()->setX(luaL_checknumber(L, 3)); break; + case Mapper::y: + data->getSkeleton()->setY(luaL_checknumber(L, 3)); break; + case Mapper::vscale: + data->getSkeleton()->setScaleX(luaL_checknumber(L, 3)); break; + case Mapper::hscale: + data->getSkeleton()->setScaleY(luaL_checknumber(L, 3)); break; + + default: + break; + } + return 0; + } + + static int update(lua_State* L) + { + GETUDATA(data, 1); + auto delta_t = luaL_checknumber(L, 2); + +#if SPINE_MAJOR_VERSION >= 4 + int physic_state = 0; + if (lua_gettop(L) >= 3) physic_state = luaL_checkint(L, 3); + + spine::Physics physics; + switch (physic_state) + { + case 0: physics = spine::Physics_Update; break; + case 1: physics = spine::Physics_None; break; + case 2: physics = spine::Physics_Pose; break; + case 3: physics = spine::Physics_Reset; break; + default: physics = spine::Physics_Update; break; + } +#endif + + auto animation_state = data->getAnimationState(); + auto skeleton = data->getSkeleton(); + animation_state->update(delta_t); + animation_state->apply(*skeleton); +#if SPINE_MAJOR_VERSION >= 4 + skeleton->updateWorldTransform(physics); +#else + skeleton->updateWorldTramsform(); +#endif + return 0; + } + static int reset(lua_State* L) + { + GETUDATA(data, 1); + auto animationState = data->getAnimationState(); + auto skeleton = data->getSkeleton(); + + skeleton->setToSetupPose(); + skeleton->setSlotsToSetupPose(); + + animationState->clearTracks(); + animationState->setTimeScale(1.0f); + animationState->setListener(spine::LuaSTGdummyOnAnimationEventFunc); + animationState->update(0); + animationState->apply(*skeleton); +#if SPINE_MAJOR_VERSION >= 4 + skeleton->updateWorldTransform(spine::Physics_Reset); +#else + skeleton->updateWorldTransform(); +#endif + + return 0; + } + static int render(lua_State* L) + { + GETUDATA(data, 1); + + auto* ctx = LAPP.GetAppModel()->getRenderer(); + core::Graphics::IRenderer::DrawVertex vertex[3]; + vertex[0].z = 0.5f; vertex[1].z = 0.5f; vertex[2].z = 0.5f; + + auto skeleton = data->getSkeleton(); + auto& skeletonRenderer = spine::LuaSTGSkeletonRenderer::Instance(); + spine::RenderCommand* command = skeletonRenderer.render(*skeleton); + while (command) { + float* positions = command->positions; + float* uvs = command->uvs; + uint32_t* colors = command->colors; + uint16_t* indices = command->indices; + int32_t n_indices = command->numIndices; + int32_t n_vertices = command->numVertices; + + core::Graphics::ITexture2D* texture = (core::Graphics::ITexture2D*)command->texture; + ctx->setTexture(texture); + + luastg::BlendMode mode; + switch (command->blendMode) + { + case spine::BlendMode_Additive: + mode = luastg::BlendMode::AddAdd; break; + case spine::BlendMode_Multiply: + mode = luastg::BlendMode::AddMutiply; break; + case spine::BlendMode_Screen: + mode = luastg::BlendMode::AddScreen; break; + case spine::BlendMode_Normal: // fall through + default: mode = luastg::BlendMode::MulAlpha; break; + } + LAPP.updateGraph2DBlendMode(mode); + + auto vertices = new core::Graphics::IRenderer::DrawVertex[n_vertices]; + for (int i = 0, n = command->numVertices; i < n; i++) + { + vertices[i].x = positions[i << 1]; + vertices[i].y = positions[(i << 1) + 1]; + vertices[i].z = 0.5f; + vertices[i].u = uvs[i << 1]; + vertices[i].v = uvs[(i << 1) + 1]; + vertices[i].color = colors[i]; + } + ctx->drawRaw(vertices, n_vertices, indices, n_indices); + delete[] vertices; + command = command->next; + } + + return 0; + } + static int getExistBones(lua_State* L) + { + GETUDATA(data, 1); + auto& bones = data->getAllBones(); + + lua_createtable(L, bones.size(), 0); // ..args t + + int count = 1; + for (const auto& [k, _] : bones) + { + lua_pushlstring(L, k.data(), k.length()); // ..args t v + lua_rawseti(L, -2, count++); // ..args t + } + + return 1; + } + static int getBoneInfo(lua_State* L) + { + GETUDATA(data, 1); + const char* bone_name = luaL_checkstring(L, 2); + + spine::Bone* bone = data->findBone(bone_name); + if (!bone) return luaL_error(L, "could not find bone '%s' from spine skeleton '%s'.", bone_name, std::string(data->getName()).c_str()); + + // 不知道这些信息哪些有用 总之全给了.jpg + lua_createtable(L, 0, 13); // ..args t + lua_pushnumber(L, bone->getX()); // ..args t val + lua_setfield(L, -2, "x"); // ..args t + lua_pushnumber(L, bone->getY()); // ..args t val + lua_setfield(L, -2, "y"); // ..args t + lua_pushnumber(L, bone->getWorldX()); // ..args t val + lua_setfield(L, -2, "world_x"); // ..args t + lua_pushnumber(L, bone->getWorldY()); // ..args t val + lua_setfield(L, -2, "world_y"); // ..args t + lua_pushnumber(L, bone->getRotation()); // ..args t val + lua_setfield(L, -2, "rot"); // ..args t + lua_pushnumber(L, bone->getWorldRotationX()); // ..args t val + lua_setfield(L, -2, "world_rot_x"); // ..args t + lua_pushnumber(L, bone->getWorldRotationY()); // ..args t val + lua_setfield(L, -2, "world_rot_y"); // ..args t + lua_pushnumber(L, bone->getScaleX()); // ..args t val + lua_setfield(L, -2, "vscale"); // ..args t + lua_pushnumber(L, bone->getScaleY()); // ..args t val + lua_setfield(L, -2, "hscale"); // ..args t + lua_pushnumber(L, bone->getWorldScaleX()); // ..args t val + lua_setfield(L, -2, "world_vscale"); // ..args t + lua_pushnumber(L, bone->getWorldScaleY()); // ..args t val + lua_setfield(L, -2, "world_hscale"); // ..args t + lua_pushnumber(L, bone->getShearX()); // ..args t val + lua_setfield(L, -2, "shear_x"); // ..args t + lua_pushnumber(L, bone->getShearY()); // ..args t val + lua_setfield(L, -2, "shear_y"); // ..args t + + return 1; + } + static int setBoneState(lua_State* L) + { + GETUDATA(data, 1); + const char* name = luaL_checkstring(L, 2); + + auto bone = data->findBone(name); + if (!bone) return luaL_error(L, "spine skeleton '%s' does not have bone '%s'.", std::string(data->getName()).c_str(), name); + + const float x = luaL_checknumber(L, 3); + const float y = luaL_checknumber(L, 4); + const float rot = luaL_checknumber(L, 5); + + float vscale = 1, hscale = 1, shear_x = 0, shear_y = 0; + const int stack_top = lua_gettop(L); + switch (stack_top) { + // using fallthrogh here + default: + case 9: + shear_y = luaL_checknumber(L, 9); + case 8: + shear_x = luaL_checknumber(L, 8); + case 7: + hscale = luaL_checknumber(L, 7); + case 6: + vscale = luaL_checknumber(L, 6); + } + + bone->updateWorldTransform(x, y, rot, vscale, hscale, shear_x, shear_y); + + return 0; + } + static int getExistAnimations(lua_State* L) + { + GETUDATA(data, 1); + auto& animations = data->getAllAnimations(); + + lua_createtable(L, animations.size(), 0); // ..args t + + int count = 1; + for (const auto& [k, _] : animations) + { + lua_pushlstring(L, k.data(), k.length()); // ..args t v + lua_rawseti(L, -2, count++); // ..args t + } + + return 1; + } + static int addAnimation(lua_State* L) + { + GETUDATA(data, 1); + const char* ani_name = luaL_checkstring(L, 2); + const size_t track_index = luaL_checkinteger(L, 3); + const float delay = luaL_checknumber(L, 4); + const bool loop = lua_toboolean(L, 5); + + + spine::Animation* ani = data->findAnimation(ani_name); + if (!ani) return luaL_error(L, "could not find animation '%s' from spine skeleton '%s'.", ani_name, std::string(data->getName()).c_str()); + + auto animationState = data->getAnimationState(); + animationState->addAnimation(track_index, ani, loop, delay); + + return 0; + } + static int setAnimation(lua_State* L) + { + GETUDATA(data, 1); + const char* ani_name = luaL_checkstring(L, 2); + const size_t track_index = luaL_checkinteger(L, 3); + const float delay = luaL_checknumber(L, 4); + const bool loop = lua_toboolean(L, 5); + + + spine::Animation* ani = data->findAnimation(ani_name); + if (!ani) return luaL_error(L, "could not find animation '%s' from spine skeleton '%s'.", ani_name, std::string(data->getName()).c_str()); + + auto animationState = data->getAnimationState(); + animationState->setAnimation(track_index, ani, loop); + + return 0; + } + static int setAnimationTimeScale(lua_State* L) + { + GETUDATA(data, 1); + const float time_scale = luaL_checknumber(L, 2); + + auto animationState = data->getAnimationState(); + const float old_time_scale = animationState->getTimeScale(); + animationState->setTimeScale(time_scale); + + lua_pushnumber(L, old_time_scale); + + return 1; + } + static int getCurrentAnimation(lua_State* L) + { + GETUDATA(data, 1); + const size_t track_index = luaL_checkinteger(L, 2); + + auto animationState = data->getAnimationState(); + auto current = animationState->getCurrent(track_index); + + if (!current) + { + lua_pushnil(L); + return 1; + } + + const auto& ani_name = current->getAnimation()->getName(); + lua_pushlstring(L, ani_name.buffer(), ani_name.length()); + + return 1; + } + static int getExistEvents(lua_State* L) + { + GETUDATA(data, 1); + const auto& events = data->getSkeleton()->getData()->getEvents(); + + const auto size = events.size(); + lua_createtable(L, size, 0); // ..args t + + int count = 1; + for (int i = 0; i < size; i++) + { + const auto& event = events[i]->getName(); + lua_pushlstring(L, event.buffer(), event.length()); // ..args t v + lua_rawseti(L, -2, count++); // ..args t + } + + return 1; + } + static int setEventListener(lua_State* L) + { + GETUDATA(data, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); // animation callback + luaL_checktype(L, 3, LUA_TFUNCTION); // event callback + + data->makeSelfWeakTable(L, 1); + data->setAnimationCallback(L, 2); + data->setEventCallback(L, 3); + + auto listener = [data](spine::AnimationState* state, spine::EventType type, spine::TrackEntry* entry, spine::Event* event) + { + lua_State* L = LAPP.GetLuaEngine(); + int stack_top = lua_gettop(L); + + if (type == spine::EventType_Event) + { + // spine name time fval ival sval ba vol + data->pushEventCallback(L); + data->pushSelf(L); + const auto& name = event->getData().getName(); + lua_pushlstring(L, name.buffer(), name.length()); + lua_pushinteger(L, event->getTime()); + lua_pushinteger(L, event->getFloatValue()); + lua_pushinteger(L, event->getIntValue()); + const auto& str = event->getStringValue(); + lua_pushlstring(L, str.buffer(), str.length()); + lua_pushnumber(L, event->getBalance()); + lua_pushnumber(L, event->getVolume()); + + lua_call(L, 8, 0); + lua_settop(L, stack_top); + return; + } + + data->pushAnimationCallback(L); + data->pushSelf(L); + const auto& name = entry->getAnimation()->getName(); + lua_pushlstring(L, name.buffer(), name.length()); + switch (type) { + case spine::EventType_Start: + lua_pushstring(L, "start"); break; + case spine::EventType_Interrupt: + lua_pushstring(L, "interrupt"); break; + case spine::EventType_End: + lua_pushstring(L, "end"); break; + case spine::EventType_Complete: + lua_pushstring(L, "complete"); break; + case spine::EventType_Dispose: + lua_pushstring(L, "dispose"); break; + default: + break; + }; + lua_call(L, 3, 0); + lua_settop(L, stack_top); + + return; + }; + + data->getAnimationState()->setListener(listener); + + + return 1; + } + static int getExistSkins(lua_State* L) + { + GETUDATA(data, 1); + auto& skins = data->getAllSkins(); + + lua_createtable(L, skins.size(), 0); // ..args t + + int count = 1; + for (const auto& [k, _] : skins) + { + lua_pushlstring(L, k.data(), k.length()); // ..args t v + lua_rawseti(L, -2, count++); // ..args t + } + + return 1; + } + static int setSkin(lua_State* L) + { + GETUDATA(data, 1); + const char* name = luaL_checkstring(L, 2); + + spine::Skin* skin = data->findSkin(name); + if (!skin) return luaL_error(L, "could not find skin '%s' from spine skeleton '%s'.", name, std::string(data->getName()).c_str()); + + const auto skeleton = data->getSkeleton(); + skeleton->setSkin(skin); + skeleton->setSlotsToSetupPose(); + data->getAnimationState()->apply(*skeleton); + + return 0; + } + static int getCurrentSkin(lua_State* L) + { + GETUDATA(data, 1); + + const auto& name = data->getSkeleton()->getSkin()->getName(); + lua_pushlstring(L, name.buffer(), name.length()); + + return 1; + } + static int getExistSlots(lua_State* L) + { + GETUDATA(data, 1); + const auto& slots = data->getAllSlots(); + + const auto size = slots.size(); + lua_createtable(L, size, 0); // ..args t + + int count = 1; + for (const auto& [_, slot] : slots) + { + const auto& name = slot.slot->getData().getName(); + lua_pushlstring(L, name.buffer(), name.length()); // ..args t v + lua_rawseti(L, -2, count++); // ..args t + } + return 1; + } + static int setSlotAttachment(lua_State* L) + { + GETUDATA(data, 1); + const char* slot_name = luaL_checkstring(L, 2); + const char* attachment_name = luaL_checkstring(L, 3); + + const auto& slot_info = data->findSlot(slot_name); + auto slot = slot_info.slot; + if (!slot) return luaL_error(L, "could not find slot '%s' from spine skeleton '%s'.", slot_name, std::string(data->getName()).c_str()); + + slot->setAttachment(data->getSkeleton()->getAttachment(slot_info.index, attachment_name)); + + return 0; + } + static int getCurrentAttachmentOnSlot(lua_State* L) + { + GETUDATA(data, 1); + const char* slot_name = luaL_checkstring(L, 2); + + const auto& slot_info = data->findSlot(slot_name); + auto slot = slot_info.slot; + if (!slot) return luaL_error(L, "could not find slot '%s' from spine skeleton '%s'.", slot_name, std::string(data->getName()).c_str()); + + auto attachment = slot->getAttachment(); + if (attachment) + { + const auto& name = attachment->getName(); + lua_pushlstring(L, name.buffer(), name.length()); + return 1; + } + + lua_pushnil(L); + return 1; + } + + }; +#undef GETUDATA + + luaL_Reg const lib[] = { + { "getExistBones", &Wrapper::getExistBones }, + { "getBoneInfo", &Wrapper::getBoneInfo }, + { "update", &Wrapper::update }, + { "reset", &Wrapper::reset }, + { "render", &Wrapper::render }, + { "getExistAnimations", &Wrapper::getExistAnimations }, + { "addAnimation", &Wrapper::addAnimation }, + { "setAnimation", &Wrapper::setAnimation }, + { "getCurrentAnimation", &Wrapper::getCurrentAnimation }, + { "getExistEvents", &Wrapper::getExistEvents }, + { "setEventListener", &Wrapper::setEventListener }, + { "getExistSkins", &Wrapper::getExistSkins }, + { "setSkin", &Wrapper::setSkin }, + { "getCurrentSkin", &Wrapper::getCurrentSkin }, + { "getExistSlots", &Wrapper::getExistSlots }, + { "setSlotAttachment", &Wrapper::setSlotAttachment }, + { "getCurrentAttachmentOnSlot", &Wrapper::getCurrentAttachmentOnSlot }, + { NULL, NULL }, + }; + + luaL_Reg const mt[] = { + { "__gc", &Wrapper::__gc }, + { "__index", &Wrapper::__index }, + { "__newindex", &Wrapper::__newindex }, + { NULL, NULL }, + }; + + luaL_Reg const ins[] = { + { "CreateSpineInstance", &Wrapper::CreateSpineInstance }, + { "CheckSpineSupport", &Wrapper::CheckSpineSupport }, + { NULL, NULL } + }; + + luaL_register(L, "lstg", ins); // ??? lstg + RegisterClassIntoTable2(L, ".Spine", lib, LUASTG_LUA_TYPENAME_SPINE, mt); + lua_pop(L, 1); +} + +void luastg::binding::Spine::CreateAndPush(lua_State* L, IResourceSpineSkeleton* data) +{ + SpineInstance* p = static_cast(lua_newuserdata(L, sizeof(SpineInstance))); // udata + new(p) SpineInstance(data->GetResName(), data->getSkeletonData(), data->getAnimationStateData()); + luaL_getmetatable(L, LUASTG_LUA_TYPENAME_SPINE); // udata mt + lua_setmetatable(L, -2); // udata +} +#else +void luastg::binding::Spine::Register(lua_State* L) noexcept +{ + struct Wrapper { + static int CreateSpineInstance(lua_State* L) + { + return luaL_error(L, "spine is not supported in this version!"); + } + static int CheckSpineSupport(lua_State* L) + { + lua_pushstring(L, "unsupport"); + return 1; + } + }; + + luaL_Reg const ins[] = { + { "CreateSpineInstance", &Wrapper::CreateSpineInstance }, + { "CheckSpineSupport", &Wrapper::CheckSpineSupport }, + { NULL, NULL } + }; + + luaL_register(L, "lstg", ins); // ??? lstg + lua_pop(L, 1); // ??? +} +#endif \ No newline at end of file diff --git a/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp b/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp index a617bc17..9e197d82 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp +++ b/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp @@ -42,6 +42,7 @@ namespace luastg::binding luaL_register(L, LUASTG_LUA_LIBNAME, constructors); // ? t Color::Register(L); ParticleSystem::Register(L); + Spine::Register(L); StopWatch::Register(L); BentLaser::Register(L); DirectInput::Register(L); diff --git a/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.hpp b/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.hpp index 01b1e5dd..3acfba15 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.hpp +++ b/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.hpp @@ -7,6 +7,7 @@ #define LUASTG_LUA_LIBNAME "lstg" +#define LUASTG_LUA_TYPENAME_SPINE "lstg.Spine" #define LUASTG_LUA_TYPENAME_STOPWATCH "lstg.StopWatch" #define LUASTG_LUA_TYPENAME_BENTLASER "lstg.CurveLaser" @@ -90,7 +91,14 @@ namespace luastg::binding static void Register(lua_State* L) noexcept; static void CreateAndPush(lua_State* L, core::Color4B const& color); }; - + class Spine + { + public: + static void Register(lua_State* L) noexcept; +#ifdef LUASTG_SUPPORTS_SPINE + static void CreateAndPush(lua_State* L, IResourceSpineSkeleton* data); +#endif + }; class StopWatch { public: diff --git a/LuaSTG/LuaSTG/LuaBinding/LuaWrapperMisc.hpp b/LuaSTG/LuaSTG/LuaBinding/LuaWrapperMisc.hpp index f1fc4faa..8c9c9c5e 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LuaWrapperMisc.hpp +++ b/LuaSTG/LuaSTG/LuaBinding/LuaWrapperMisc.hpp @@ -56,7 +56,7 @@ namespace luastg //在栈顶的table创建一个名为name的新表,然后注册静态方法到该表中 //注册元方法到注册表中名为metaname的元表中,并保护元表不被修改 //不注册index元方法 - inline void RegisterClassIntoTable2(lua_State* L, const char* name, luaL_Reg* method, const char* metaname, luaL_Reg* metamethod) + inline void RegisterClassIntoTable2(lua_State* L, const char* name, luaL_Reg const* method, const char* metaname, luaL_Reg const* metamethod) { // ... t lua_pushstring(L, name); // ... t s //key diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index bcbdb632..3a6651c1 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -140,6 +140,29 @@ target_sources(libqoi PRIVATE set_target_properties(libqoi PROPERTIES FOLDER external) +# ==================== spine-cpp ==================== + +if(LUASTG_LINK_SPINE_RUNTIME) + add_library(spine-cpp STATIC) + target_compile_definitions(spine-cpp PUBLIC + _CRT_SECURE_NO_WARNINGS + SPINE_USE_STD_FUNCTION + ) + target_include_directories(spine-cpp PUBLIC + spine-runtimes/spine-cpp/spine-cpp/include + ) + + file(GLOB SPINE_INCLUDES "spine-runtimes/spine-cpp/spine-cpp/include/**/*.h") + file(GLOB SPINE_SOURCES "spine-runtimes/spine-cpp/spine-cpp/src/**/*.cpp") + + target_sources(spine-cpp PRIVATE + ${SPINE_INCLUDES} + ${SPINE_SOURCES} + ) + + set_target_properties(spine-cpp PROPERTIES FOLDER external) +endif() + # ==================== steam api ==================== add_subdirectory(steam_api) diff --git a/external/spine-runtimes b/external/spine-runtimes new file mode 160000 index 00000000..36535405 --- /dev/null +++ b/external/spine-runtimes @@ -0,0 +1 @@ +Subproject commit 3653540558ba09104065addc56178dc2bceafc24