diff --git a/BusyRabbit.uproject.DotSettings.user b/BusyRabbit.uproject.DotSettings.user new file mode 100644 index 0000000..9370ce5 --- /dev/null +++ b/BusyRabbit.uproject.DotSettings.user @@ -0,0 +1,5 @@ + + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded \ No newline at end of file diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index aec5a8b..cab2e2a 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -20,5 +20,11 @@ GameDefaultMap=/Game/Level/HomeLand.HomeLand [/Script/Engine.RendererSettings] r.Mobile.AntiAliasing=0 -r.AntiAliasingMethod=2 +r.AntiAliasingMethod=3 +r.DefaultFeature.MotionBlur=False + +[CoreRedirects] ++PropertyRedirects=(OldName="/Script/BusyRabbit.TerrainTileSetConfig.TerrainTileMapping",NewName="/Script/BusyRabbit.TerrainTileSetConfig.n") ++PropertyRedirects=(OldName="/Script/BusyRabbit.TerrainLayerComponent.TerrainLayers",NewName="/Script/BusyRabbit.TerrainLayerComponent.TerrainMeshes") ++ClassRedirects=(OldName="/Script/BusyRabbit.LevelPlayerController",NewName="/Script/BusyRabbit.LevelPlayerController") diff --git a/Config/DefaultGameplayTags.ini b/Config/DefaultGameplayTags.ini index a886ef0..e1885d6 100644 --- a/Config/DefaultGameplayTags.ini +++ b/Config/DefaultGameplayTags.ini @@ -35,12 +35,14 @@ NetIndexFirstBitSegment=16 +GameplayTagList=(Tag="Recover.Role.Health",DevComment="回复生命值") +GameplayTagList=(Tag="Recover.Role.Hunger",DevComment="恢复饥饿值") +GameplayTagList=(Tag="Status.Role.Invincible",DevComment="不掉血标签") ++GameplayTagList=(Tag="Terrain.Desert",DevComment="荒漠地形") +GameplayTagList=(Tag="Terrain.Forest",DevComment="森林") +GameplayTagList=(Tag="Terrain.Grassland",DevComment="草地") -+GameplayTagList=(Tag="Terrain.Water",DevComment="水体") -+GameplayTagList=(Tag="Terrain.Water.Shallow", DevComment="浅水区") -+GameplayTagList=(Tag="Terrain.Water.Deep", DevComment="深水区") +GameplayTagList=(Tag="Terrain.Land",DevComment="土地") -+GameplayTagList=(Tag="Terrain.Swamp", DevComment="沼泽") -+GameplayTagList=(Tag="Terrain.Swamp.Land", DevComment="沼泽陆地") -+GameplayTagList=(Tag="Terrain.Swamp.Water", DevComment="沼泽水域") ++GameplayTagList=(Tag="Terrain.Swamp",DevComment="沼泽") ++GameplayTagList=(Tag="Terrain.Swamp.Land",DevComment="沼泽陆地") ++GameplayTagList=(Tag="Terrain.Swamp.Water",DevComment="沼泽水域") ++GameplayTagList=(Tag="Terrain.Water",DevComment="水体") ++GameplayTagList=(Tag="Terrain.Water.Deep",DevComment="深水区") ++GameplayTagList=(Tag="Terrain.Water.Shallow",DevComment="浅水区") + diff --git a/Content/Blueprint/Bp_BusyCharacter.uasset b/Content/Blueprint/Bp_BusyCharacter.uasset deleted file mode 100644 index 1a3d134..0000000 Binary files a/Content/Blueprint/Bp_BusyCharacter.uasset and /dev/null differ diff --git a/Content/Blueprint/Bp_BusyRole.uasset b/Content/Blueprint/Bp_BusyRole.uasset index 371f598..38dcbca 100644 Binary files a/Content/Blueprint/Bp_BusyRole.uasset and b/Content/Blueprint/Bp_BusyRole.uasset differ diff --git a/Content/Blueprint/Bp_HomelandGameMode.uasset b/Content/Blueprint/Bp_HomelandGameMode.uasset deleted file mode 100644 index 24e0ca4..0000000 Binary files a/Content/Blueprint/Bp_HomelandGameMode.uasset and /dev/null differ diff --git a/Content/Blueprint/Level/Actor/Holder.uasset b/Content/Blueprint/Level/Actor/Holder.uasset new file mode 100644 index 0000000..ed7c905 Binary files /dev/null and b/Content/Blueprint/Level/Actor/Holder.uasset differ diff --git a/Content/Blueprint/Level/Actor/Role/BP_Rabbit.uasset b/Content/Blueprint/Level/Actor/Role/BP_Rabbit.uasset new file mode 100644 index 0000000..2de2771 Binary files /dev/null and b/Content/Blueprint/Level/Actor/Role/BP_Rabbit.uasset differ diff --git a/Content/Blueprint/Level/BP_LevelMap.uasset b/Content/Blueprint/Level/BP_LevelMap.uasset new file mode 100644 index 0000000..e6e6b2e Binary files /dev/null and b/Content/Blueprint/Level/BP_LevelMap.uasset differ diff --git a/Content/Blueprint/Level/GameMode/BP_BusyLevelGameMode.uasset b/Content/Blueprint/Level/GameMode/BP_BusyLevelGameMode.uasset new file mode 100644 index 0000000..ee2dc77 Binary files /dev/null and b/Content/Blueprint/Level/GameMode/BP_BusyLevelGameMode.uasset differ diff --git a/Content/Blueprint/Level/GameMode/BP_BusyLevelPlayerController.uasset b/Content/Blueprint/Level/GameMode/BP_BusyLevelPlayerController.uasset new file mode 100644 index 0000000..6e70b90 Binary files /dev/null and b/Content/Blueprint/Level/GameMode/BP_BusyLevelPlayerController.uasset differ diff --git a/Content/Blueprint/Level/GameMode/BP_BusyLevelPlayerState.uasset b/Content/Blueprint/Level/GameMode/BP_BusyLevelPlayerState.uasset new file mode 100644 index 0000000..0e3a53c Binary files /dev/null and b/Content/Blueprint/Level/GameMode/BP_BusyLevelPlayerState.uasset differ diff --git a/Content/Data/Asset/Test.uasset b/Content/Data/Asset/Test.uasset new file mode 100644 index 0000000..ee2a83c Binary files /dev/null and b/Content/Data/Asset/Test.uasset differ diff --git a/Content/Data/Input/Level/IA_Move.uasset b/Content/Data/Input/Level/IA_Move.uasset new file mode 100644 index 0000000..8753fba Binary files /dev/null and b/Content/Data/Input/Level/IA_Move.uasset differ diff --git a/Content/Data/Input/Level/IMC_PlayerInputContext.uasset b/Content/Data/Input/Level/IMC_PlayerInputContext.uasset new file mode 100644 index 0000000..ff99710 Binary files /dev/null and b/Content/Data/Input/Level/IMC_PlayerInputContext.uasset differ diff --git a/Content/Level/BP_Test.uasset b/Content/Level/BP_Test.uasset deleted file mode 100644 index c737e3d..0000000 Binary files a/Content/Level/BP_Test.uasset and /dev/null differ diff --git a/Content/Level/FalconPlain.umap b/Content/Level/FalconPlain.umap index 27a48ed..a0fcc07 100644 Binary files a/Content/Level/FalconPlain.umap and b/Content/Level/FalconPlain.umap differ diff --git a/Content/Level/HomeLand.umap b/Content/Level/HomeLand.umap index e2fdc8e..aaac49d 100644 Binary files a/Content/Level/HomeLand.umap and b/Content/Level/HomeLand.umap differ diff --git a/Content/Level/NewWorld.umap b/Content/Level/NewWorld.umap deleted file mode 100644 index bf5375d..0000000 Binary files a/Content/Level/NewWorld.umap and /dev/null differ diff --git a/Content/Level/tileset.uasset b/Content/Level/tileset.uasset deleted file mode 100644 index 1b5cb55..0000000 Binary files a/Content/Level/tileset.uasset and /dev/null differ diff --git a/Content/Lua/GamePlay/Level/BusyLevelLogicSubSystem.lua b/Content/Lua/GamePlay/Level/BusyLevelLogicSubSystem.lua index 64282bb..5e6abe0 100644 --- a/Content/Lua/GamePlay/Level/BusyLevelLogicSubSystem.lua +++ b/Content/Lua/GamePlay/Level/BusyLevelLogicSubSystem.lua @@ -42,11 +42,11 @@ function SubSystem:ReceiveWorldBeginPlay() self.generator = CreateItemGenerator(row_data) -- 创建初始篝火 - local bonfire = BusyActorManagerSubSystem:SpawnBonfire(row_data.FirstBonfirePosition) + -- local bonfire = BusyActorManagerSubSystem:SpawnBonfire(row_data.FirstBonfirePosition) -- 创建角色 - local role = BusyActorManagerSubSystem:SpawnRole(bonfire) - GameplayStatics.GetPlayerController(self, 0):Possess(role) + -- local role = BusyActorManagerSubSystem:SpawnRole(bonfire) + -- GameplayStatics.GetPlayerController(self, 0):Possess(role) end function SubSystem:ReceiveSubSystemTick(DeltaTime) diff --git a/Content/Lua/Level/Actor/BusyPlayerRole.lua b/Content/Lua/Level/Actor/BusyPlayerRole.lua new file mode 100644 index 0000000..68878ea --- /dev/null +++ b/Content/Lua/Level/Actor/BusyPlayerRole.lua @@ -0,0 +1,11 @@ +local BusyPlayerRole = {} + +function BusyPlayerRole:UpdateMoveDirection(InDirection) + if(InDirection.Y > 0) then + self["SpineAnimationComponent"]:SetSkin("front/move") + else + self["SpineAnimationComponent"]:SetSkin("back/move") + end +end + +return Class(nil, nil, BusyPlayerRole) diff --git a/Content/Resource/Animation/Item/apple_Flipbook.uasset b/Content/Resource/Animation/Item/apple_Flipbook.uasset deleted file mode 100644 index 59e58f2..0000000 Binary files a/Content/Resource/Animation/Item/apple_Flipbook.uasset and /dev/null differ diff --git a/Content/Resource/Animation/Item/macadam_Filpbook.uasset b/Content/Resource/Animation/Item/macadam_Filpbook.uasset deleted file mode 100644 index 36f0bdb..0000000 Binary files a/Content/Resource/Animation/Item/macadam_Filpbook.uasset and /dev/null differ diff --git a/Content/Resource/Map/FalconPlain/TerrainGrid/desert-tilemap.uasset b/Content/Resource/Map/FalconPlain/TerrainGrid/desert-tilemap.uasset new file mode 100644 index 0000000..8f780c4 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/TerrainGrid/desert-tilemap.uasset differ diff --git a/Content/Resource/Map/FalconPlain/TerrainGrid/desert-tilemap_TileSet.uasset b/Content/Resource/Map/FalconPlain/TerrainGrid/desert-tilemap_TileSet.uasset new file mode 100644 index 0000000..5063b88 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/TerrainGrid/desert-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Map/FalconPlain/TerrainGrid/grass-tilemap.uasset b/Content/Resource/Map/FalconPlain/TerrainGrid/grass-tilemap.uasset new file mode 100644 index 0000000..194cc42 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/TerrainGrid/grass-tilemap.uasset differ diff --git a/Content/Resource/Map/FalconPlain/TerrainGrid/grass-tilemap_TileSet.uasset b/Content/Resource/Map/FalconPlain/TerrainGrid/grass-tilemap_TileSet.uasset new file mode 100644 index 0000000..5faf2c3 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/TerrainGrid/grass-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Map/FalconPlain/TerrainGrid/shallow-water-tilemap.uasset b/Content/Resource/Map/FalconPlain/TerrainGrid/shallow-water-tilemap.uasset new file mode 100644 index 0000000..0f60a8b Binary files /dev/null and b/Content/Resource/Map/FalconPlain/TerrainGrid/shallow-water-tilemap.uasset differ diff --git a/Content/Level/tileset_TileSet.uasset b/Content/Resource/Map/FalconPlain/TerrainGrid/shallow-water-tilemap_TileSet.uasset similarity index 52% rename from Content/Level/tileset_TileSet.uasset rename to Content/Resource/Map/FalconPlain/TerrainGrid/shallow-water-tilemap_TileSet.uasset index b282d02..b860891 100644 Binary files a/Content/Level/tileset_TileSet.uasset and b/Content/Resource/Map/FalconPlain/TerrainGrid/shallow-water-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Map/FalconPlain/Test.uasset b/Content/Resource/Map/FalconPlain/Test.uasset index 6b24590..414b774 100644 Binary files a/Content/Resource/Map/FalconPlain/Test.uasset and b/Content/Resource/Map/FalconPlain/Test.uasset differ diff --git a/Content/Resource/Map/FalconPlain/Test/NewBlueprint.uasset b/Content/Resource/Map/FalconPlain/Test/NewBlueprint.uasset new file mode 100644 index 0000000..b0417a3 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/Test/NewBlueprint.uasset differ diff --git a/Content/Resource/Map/FalconPlain/Test/grass-tilemap1.uasset b/Content/Resource/Map/FalconPlain/Test/grass-tilemap1.uasset new file mode 100644 index 0000000..564c39d Binary files /dev/null and b/Content/Resource/Map/FalconPlain/Test/grass-tilemap1.uasset differ diff --git a/Content/Resource/Map/FalconPlain/Test/swamp-water-tilemap.uasset b/Content/Resource/Map/FalconPlain/Test/swamp-water-tilemap.uasset new file mode 100644 index 0000000..4dcc775 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/Test/swamp-water-tilemap.uasset differ diff --git a/Content/Resource/Map/FalconPlain/Test/swamp-water-tilemap_Sprite.uasset b/Content/Resource/Map/FalconPlain/Test/swamp-water-tilemap_Sprite.uasset new file mode 100644 index 0000000..4478fc2 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/Test/swamp-water-tilemap_Sprite.uasset differ diff --git a/Content/Resource/Map/FalconPlain/Test/swamp-water-tilemap_TileSet.uasset b/Content/Resource/Map/FalconPlain/Test/swamp-water-tilemap_TileSet.uasset new file mode 100644 index 0000000..267f6d7 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/Test/swamp-water-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Map/FalconPlain/deep-water-tilemap.uasset b/Content/Resource/Map/FalconPlain/deep-water-tilemap.uasset new file mode 100644 index 0000000..d9e8872 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/deep-water-tilemap.uasset differ diff --git a/Content/Resource/Map/FalconPlain/deep-water-tilemap_TileSet.uasset b/Content/Resource/Map/FalconPlain/deep-water-tilemap_TileSet.uasset new file mode 100644 index 0000000..dbeb3e4 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/deep-water-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Map/FalconPlain/grass-tilemap_TileSet.uasset b/Content/Resource/Map/FalconPlain/grass-tilemap_TileSet.uasset new file mode 100644 index 0000000..82ecf06 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/grass-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Map/FalconPlain/lack-tilemap.uasset b/Content/Resource/Map/FalconPlain/lack-tilemap.uasset index 19b0ab0..8bef309 100644 Binary files a/Content/Resource/Map/FalconPlain/lack-tilemap.uasset and b/Content/Resource/Map/FalconPlain/lack-tilemap.uasset differ diff --git a/Content/Resource/Map/FalconPlain/lack-tilemap_TileSet.uasset b/Content/Resource/Map/FalconPlain/lack-tilemap_TileSet.uasset index 5768f0e..cc61b67 100644 Binary files a/Content/Resource/Map/FalconPlain/lack-tilemap_TileSet.uasset and b/Content/Resource/Map/FalconPlain/lack-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Map/FalconPlain/land-tilemap.uasset b/Content/Resource/Map/FalconPlain/land-tilemap.uasset new file mode 100644 index 0000000..80381a1 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/land-tilemap.uasset differ diff --git a/Content/Resource/Map/FalconPlain/land-tilemap_TileSet.uasset b/Content/Resource/Map/FalconPlain/land-tilemap_TileSet.uasset new file mode 100644 index 0000000..76ab4d9 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/land-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Map/FalconPlain/swamp-land-tilemap.uasset b/Content/Resource/Map/FalconPlain/swamp-land-tilemap.uasset new file mode 100644 index 0000000..096ba54 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/swamp-land-tilemap.uasset differ diff --git a/Content/Resource/Map/FalconPlain/swamp-land-tilemap_TileSet.uasset b/Content/Resource/Map/FalconPlain/swamp-land-tilemap_TileSet.uasset new file mode 100644 index 0000000..3cacf68 Binary files /dev/null and b/Content/Resource/Map/FalconPlain/swamp-land-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Map/FalconPlain/swamp-water-tilemap.uasset b/Content/Resource/Map/FalconPlain/swamp-water-tilemap.uasset new file mode 100644 index 0000000..7e8498c Binary files /dev/null and b/Content/Resource/Map/FalconPlain/swamp-water-tilemap.uasset differ diff --git a/Content/Resource/Map/FalconPlain/swamp-water-tilemap_TileSet.uasset b/Content/Resource/Map/FalconPlain/swamp-water-tilemap_TileSet.uasset new file mode 100644 index 0000000..ee185bb Binary files /dev/null and b/Content/Resource/Map/FalconPlain/swamp-water-tilemap_TileSet.uasset differ diff --git a/Content/Resource/Spine/Role/Rabbit/Rabbit.uasset b/Content/Resource/Spine/Role/Rabbit/Rabbit.uasset new file mode 100644 index 0000000..cf2b3ec Binary files /dev/null and b/Content/Resource/Spine/Role/Rabbit/Rabbit.uasset differ diff --git a/Content/Resource/Spine/Role/Rabbit/RabbitData.uasset b/Content/Resource/Spine/Role/Rabbit/RabbitData.uasset new file mode 100644 index 0000000..4eba779 Binary files /dev/null and b/Content/Resource/Spine/Role/Rabbit/RabbitData.uasset differ diff --git a/Content/Resource/Spine/Role/Rabbit/Textures/Rabbit.uasset b/Content/Resource/Spine/Role/Rabbit/Textures/Rabbit.uasset new file mode 100644 index 0000000..e0e10be Binary files /dev/null and b/Content/Resource/Spine/Role/Rabbit/Textures/Rabbit.uasset differ diff --git a/Content/Resource/Texture/Items/Textures/ItemIcons.uasset b/Content/Resource/Texture/Items/Textures/ItemIcons.uasset index 6257d81..554fca4 100644 Binary files a/Content/Resource/Texture/Items/Textures/ItemIcons.uasset and b/Content/Resource/Texture/Items/Textures/ItemIcons.uasset differ diff --git a/Plugins/SpinePlugin/Content/SpineLitNormalMaterial.uasset b/Plugins/SpinePlugin/Content/SpineLitNormalMaterial.uasset new file mode 100644 index 0000000..b0a92fc Binary files /dev/null and b/Plugins/SpinePlugin/Content/SpineLitNormalMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Content/SpineUnlitAdditiveMaterial.uasset b/Plugins/SpinePlugin/Content/SpineUnlitAdditiveMaterial.uasset new file mode 100644 index 0000000..26d0e83 Binary files /dev/null and b/Plugins/SpinePlugin/Content/SpineUnlitAdditiveMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Content/SpineUnlitMultiplyMaterial.uasset b/Plugins/SpinePlugin/Content/SpineUnlitMultiplyMaterial.uasset new file mode 100644 index 0000000..fd3eea9 Binary files /dev/null and b/Plugins/SpinePlugin/Content/SpineUnlitMultiplyMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Content/SpineUnlitNormalMaterial.uasset b/Plugins/SpinePlugin/Content/SpineUnlitNormalMaterial.uasset new file mode 100644 index 0000000..bea17a3 Binary files /dev/null and b/Plugins/SpinePlugin/Content/SpineUnlitNormalMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Content/SpineUnlitScreenMaterial.uasset b/Plugins/SpinePlugin/Content/SpineUnlitScreenMaterial.uasset new file mode 100644 index 0000000..541f616 Binary files /dev/null and b/Plugins/SpinePlugin/Content/SpineUnlitScreenMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Content/TestMaterial.uasset b/Plugins/SpinePlugin/Content/TestMaterial.uasset new file mode 100644 index 0000000..acdf04d Binary files /dev/null and b/Plugins/SpinePlugin/Content/TestMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Content/UI_SpineUnlitAdditiveMaterial.uasset b/Plugins/SpinePlugin/Content/UI_SpineUnlitAdditiveMaterial.uasset new file mode 100644 index 0000000..bdd7406 Binary files /dev/null and b/Plugins/SpinePlugin/Content/UI_SpineUnlitAdditiveMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Content/UI_SpineUnlitMultiplyMaterial.uasset b/Plugins/SpinePlugin/Content/UI_SpineUnlitMultiplyMaterial.uasset new file mode 100644 index 0000000..4cb8de5 Binary files /dev/null and b/Plugins/SpinePlugin/Content/UI_SpineUnlitMultiplyMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Content/UI_SpineUnlitNormalMaterial.uasset b/Plugins/SpinePlugin/Content/UI_SpineUnlitNormalMaterial.uasset new file mode 100644 index 0000000..c42aa44 Binary files /dev/null and b/Plugins/SpinePlugin/Content/UI_SpineUnlitNormalMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Content/UI_SpineUnlitScreenMaterial.uasset b/Plugins/SpinePlugin/Content/UI_SpineUnlitScreenMaterial.uasset new file mode 100644 index 0000000..9547cf6 Binary files /dev/null and b/Plugins/SpinePlugin/Content/UI_SpineUnlitScreenMaterial.uasset differ diff --git a/Plugins/SpinePlugin/Resources/Icon128.png b/Plugins/SpinePlugin/Resources/Icon128.png new file mode 100644 index 0000000..a20ae08 Binary files /dev/null and b/Plugins/SpinePlugin/Resources/Icon128.png differ diff --git a/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineAtlasImportFactory.cpp b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineAtlasImportFactory.cpp new file mode 100644 index 0000000..d415330 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineAtlasImportFactory.cpp @@ -0,0 +1,143 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineAtlasImportFactory.h" +#include "AssetToolsModule.h" +#include "SpineAtlasAsset.h" +#include "Editor.h" + +#define LOCTEXT_NAMESPACE "Spine" + +using namespace spine; + +USpineAtlasAssetFactory::USpineAtlasAssetFactory(const FObjectInitializer &objectInitializer) : Super(objectInitializer) { + bCreateNew = false; + bEditAfterNew = true; + bEditorImport = true; + SupportedClass = USpineAtlasAsset::StaticClass(); + + Formats.Add(TEXT("atlas;Spine Atlas file")); +} + +FText USpineAtlasAssetFactory::GetToolTip() const { + return LOCTEXT("SpineAtlasAssetFactory", "Animations exported from Spine"); +} + +bool USpineAtlasAssetFactory::FactoryCanImport(const FString &Filename) { + return true; +} + +UObject *USpineAtlasAssetFactory::FactoryCreateFile(UClass *InClass, UObject *InParent, FName InName, EObjectFlags Flags, const FString &Filename, const TCHAR *Parms, FFeedbackContext *Warn, bool &bOutOperationCanceled) { + FString FileExtension = FPaths::GetExtension(Filename); + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, *FileExtension); + + FString rawString; + if (!FFileHelper::LoadFileToString(rawString, *Filename)) { + return nullptr; + } + + FString currentSourcePath, filenameNoExtension, unusedExtension; + const FString longPackagePath = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetPathName()); + FPaths::Split(UFactory::GetCurrentFilename(), currentSourcePath, filenameNoExtension, unusedExtension); + + USpineAtlasAsset *asset = NewObject(InParent, InClass, InName, Flags); + asset->SetRawData(rawString); + asset->SetAtlasFileName(FName(*Filename)); + LoadAtlas(asset, currentSourcePath, longPackagePath); + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, asset); + return asset; +} + +bool USpineAtlasAssetFactory::CanReimport(UObject *Obj, TArray &OutFilenames) { + USpineAtlasAsset *asset = Cast(Obj); + if (!asset) return false; + + FString filename = asset->GetAtlasFileName().ToString(); + if (!filename.IsEmpty()) + OutFilenames.Add(filename); + + return true; +} + +void USpineAtlasAssetFactory::SetReimportPaths(UObject *Obj, const TArray &NewReimportPaths) { + USpineAtlasAsset *asset = Cast(Obj); + + if (asset && ensure(NewReimportPaths.Num() == 1)) + asset->SetAtlasFileName(FName(*NewReimportPaths[0])); +} + +EReimportResult::Type USpineAtlasAssetFactory::Reimport(UObject *Obj) { + USpineAtlasAsset *asset = Cast(Obj); + FString rawString; + if (!FFileHelper::LoadFileToString(rawString, *asset->GetAtlasFileName().ToString())) return EReimportResult::Failed; + asset->SetRawData(rawString); + + FString currentSourcePath, filenameNoExtension, unusedExtension; + const FString longPackagePath = FPackageName::GetLongPackagePath(asset->GetOutermost()->GetPathName()); + FString currentFileName = asset->GetAtlasFileName().ToString(); + FPaths::Split(currentFileName, currentSourcePath, filenameNoExtension, unusedExtension); + + LoadAtlas(asset, currentSourcePath, longPackagePath); + + if (Obj->GetOuter()) Obj->GetOuter()->MarkPackageDirty(); + else + Obj->MarkPackageDirty(); + + GEditor->GetEditorSubsystem()->BroadcastAssetReimport(asset); + return EReimportResult::Succeeded; +} + +UTexture2D *resolveTexture(USpineAtlasAsset *Asset, const FString &PageFileName, const FString &TargetSubPath) { + FAssetToolsModule &AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); + + TArray fileNames; + fileNames.Add(PageFileName); + + TArray importedAsset = AssetToolsModule.Get().ImportAssets(fileNames, TargetSubPath); + UTexture2D *texture = (importedAsset.Num() > 0) ? Cast(importedAsset[0]) : nullptr; + + return texture; +} + +void USpineAtlasAssetFactory::LoadAtlas(USpineAtlasAsset *Asset, const FString &CurrentSourcePath, const FString &LongPackagePath) { + Atlas *atlas = Asset->GetAtlas(); + Asset->atlasPages.Empty(); + + const FString targetTexturePath = LongPackagePath / TEXT("Textures"); + + Vector &pages = atlas->getPages(); + for (size_t i = 0, n = pages.size(); i < n; i++) { + AtlasPage *page = pages[i]; + const FString sourceTextureFilename = FPaths::Combine(*CurrentSourcePath, UTF8_TO_TCHAR(page->name.buffer())); + UTexture2D *texture = resolveTexture(Asset, sourceTextureFilename, targetTexturePath); + Asset->atlasPages.Add(texture); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineEditorPlugin.cpp b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineEditorPlugin.cpp new file mode 100644 index 0000000..d912358 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineEditorPlugin.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineEditorPlugin.h" +#include "AssetTypeActions_Base.h" +#include "SpineAtlasAsset.h" +#include "SpineSkeletonDataAsset.h" + +class FSpineAtlasAssetTypeActions : public FAssetTypeActions_Base { +public: + UClass *GetSupportedClass() const override { return USpineAtlasAsset::StaticClass(); }; + FText GetName() const override { return INVTEXT("Spine atlas asset"); }; + FColor GetTypeColor() const override { return FColor::Red; }; + uint32 GetCategories() override { return EAssetTypeCategories::Misc; }; +}; + +class FSpineSkeletonDataAssetTypeActions : public FAssetTypeActions_Base { +public: + UClass *GetSupportedClass() const override { return USpineSkeletonDataAsset::StaticClass(); }; + FText GetName() const override { return INVTEXT("Spine data asset"); }; + FColor GetTypeColor() const override { return FColor::Red; }; + uint32 GetCategories() override { return EAssetTypeCategories::Misc; }; +}; + +class FSpineEditorPlugin : public ISpineEditorPlugin { + virtual void StartupModule() override; + virtual void ShutdownModule() override; + TSharedPtr SpineAtlasAssetTypeActions; + TSharedPtr SpineSkeletonDataAssetTypeActions; +}; + +IMPLEMENT_MODULE(FSpineEditorPlugin, SpineEditorPlugin) + +void FSpineEditorPlugin::StartupModule() { + SpineAtlasAssetTypeActions = MakeShared(); + FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(SpineAtlasAssetTypeActions.ToSharedRef()); + SpineSkeletonDataAssetTypeActions = MakeShared(); + FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(SpineSkeletonDataAssetTypeActions.ToSharedRef()); +} + +void FSpineEditorPlugin::ShutdownModule() { + if (!FModuleManager::Get().IsModuleLoaded("AssetTools")) return; + FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(SpineAtlasAssetTypeActions.ToSharedRef()); + FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(SpineSkeletonDataAssetTypeActions.ToSharedRef()); +} diff --git a/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineSkeletonImportFactory.cpp b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineSkeletonImportFactory.cpp new file mode 100644 index 0000000..b27dcac --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineSkeletonImportFactory.cpp @@ -0,0 +1,126 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineSkeletonImportFactory.h" +#include "AssetToolsModule.h" +#include "Developer/AssetTools/Public/IAssetTools.h" +#include "SpineSkeletonDataAsset.h" +#include + +#define LOCTEXT_NAMESPACE "Spine" + +USpineSkeletonAssetFactory::USpineSkeletonAssetFactory(const FObjectInitializer &objectInitializer) : Super(objectInitializer) { + bCreateNew = false; + bEditAfterNew = true; + bEditorImport = true; + SupportedClass = USpineSkeletonDataAsset::StaticClass(); + + Formats.Add(TEXT("json;Spine skeleton file")); + Formats.Add(TEXT("skel;Spine skeleton file")); +} + +FText USpineSkeletonAssetFactory::GetToolTip() const { + return LOCTEXT("USpineSkeletonAssetFactory", "Animations exported from Spine"); +} + +bool USpineSkeletonAssetFactory::FactoryCanImport(const FString &Filename) { + if (Filename.Contains(TEXT(".skel"))) return true; + + if (Filename.Contains(TEXT(".json"))) { + TArray rawData; + if (!FFileHelper::LoadFileToArray(rawData, *Filename, 0)) { + return false; + } + if (rawData.Num() == 0) return false; + return strcmp((const char *) rawData.GetData(), "skeleton") > 0 && strcmp((const char *) rawData.GetData(), "spine") > 0; + } + + return false; +} + +void LoadAtlas(const FString &Filename, const FString &TargetPath) { + FAssetToolsModule &AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); + + FString skelFile = Filename.Replace(TEXT(".skel"), TEXT(".atlas")).Replace(TEXT(".json"), TEXT(".atlas")); + if (!FPaths::FileExists(skelFile)) return; + + TArray fileNames; + fileNames.Add(skelFile); + AssetToolsModule.Get().ImportAssets(fileNames, TargetPath); +} + +UObject *USpineSkeletonAssetFactory::FactoryCreateFile(UClass *InClass, UObject *InParent, FName InName, EObjectFlags Flags, const FString &Filename, const TCHAR *Parms, FFeedbackContext *Warn, bool &bOutOperationCanceled) { + USpineSkeletonDataAsset *asset = NewObject(InParent, InClass, InName, Flags); + TArray rawData; + if (!FFileHelper::LoadFileToArray(rawData, *Filename, 0)) { + return nullptr; + } + asset->SetSkeletonDataFileName(FName(*Filename)); + asset->SetRawData(rawData); + + const FString longPackagePath = FPackageName::GetLongPackagePath(asset->GetOutermost()->GetPathName()); + LoadAtlas(Filename, longPackagePath); + return asset; +} + +bool USpineSkeletonAssetFactory::CanReimport(UObject *Obj, TArray &OutFilenames) { + USpineSkeletonDataAsset *asset = Cast(Obj); + if (!asset) return false; + + FString filename = asset->GetSkeletonDataFileName().ToString(); + if (!filename.IsEmpty()) + OutFilenames.Add(filename); + + return true; +} + +void USpineSkeletonAssetFactory::SetReimportPaths(UObject *Obj, const TArray &NewReimportPaths) { + USpineSkeletonDataAsset *asset = Cast(Obj); + + if (asset && ensure(NewReimportPaths.Num() == 1)) + asset->SetSkeletonDataFileName(FName(*NewReimportPaths[0])); +} + +EReimportResult::Type USpineSkeletonAssetFactory::Reimport(UObject *Obj) { + USpineSkeletonDataAsset *asset = Cast(Obj); + TArray rawData; + if (!FFileHelper::LoadFileToArray(rawData, *asset->GetSkeletonDataFileName().ToString(), 0)) return EReimportResult::Failed; + asset->SetRawData(rawData); + + const FString longPackagePath = FPackageName::GetLongPackagePath(asset->GetOutermost()->GetPathName()); + LoadAtlas(*asset->GetSkeletonDataFileName().ToString(), longPackagePath); + + if (Obj->GetOuter()) Obj->GetOuter()->MarkPackageDirty(); + else + Obj->MarkPackageDirty(); + + return EReimportResult::Succeeded; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public/SpineAtlasImportFactory.h b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public/SpineAtlasImportFactory.h new file mode 100644 index 0000000..054ec3b --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public/SpineAtlasImportFactory.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +// clang-format off +#include "SpineAtlasAsset.h" +#include "UnrealEd.h" +#include "SpineAtlasImportFactory.generated.h" +// clang-format on + +UCLASS() +class USpineAtlasAssetFactory : public UFactory, public FReimportHandler { + GENERATED_UCLASS_BODY() + + virtual FText GetToolTip() const override; + + virtual bool FactoryCanImport(const FString &Filename) override; + virtual UObject *FactoryCreateFile(UClass *InClass, UObject *InParent, FName InName, EObjectFlags Flags, const FString &Filename, const TCHAR *Parms, FFeedbackContext *Warn, bool &bOutOperationCanceled) override; + + virtual bool CanReimport(UObject *Obj, TArray &OutFilenames) override; + virtual void SetReimportPaths(UObject *Obj, const TArray &NewReimportPaths) override; + virtual EReimportResult::Type Reimport(UObject *Obj) override; + + void LoadAtlas(USpineAtlasAsset *Asset, const FString &CurrentSourcePath, const FString &LongPackagePath); +}; diff --git a/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public/SpineEditorPlugin.h b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public/SpineEditorPlugin.h new file mode 100644 index 0000000..86ce55a --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public/SpineEditorPlugin.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include "Modules/ModuleManager.h" + +class ISpineEditorPlugin : public IModuleInterface { + +public: + static inline ISpineEditorPlugin &Get() { + return FModuleManager::LoadModuleChecked("SpineEditorPlugin"); + } + + static inline bool IsAvailable() { + return FModuleManager::Get().IsModuleLoaded("SpineEditorPlugin"); + } +}; diff --git a/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public/SpineSkeletonImportFactory.h b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public/SpineSkeletonImportFactory.h new file mode 100644 index 0000000..652113a --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpineEditorPlugin/Public/SpineSkeletonImportFactory.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +// clang-format off +#include "SpineAtlasAsset.h" +#include "UnrealEd.h" +#include "SpineSkeletonImportFactory.generated.h" +// clang-format on + +UCLASS() +class USpineSkeletonAssetFactory : public UFactory, public FReimportHandler { + GENERATED_UCLASS_BODY() + + virtual FText GetToolTip() const override; + virtual bool FactoryCanImport(const FString &Filename) override; + virtual UObject *FactoryCreateFile(UClass *InClass, UObject *InParent, FName InName, EObjectFlags Flags, const FString &Filename, const TCHAR *Parms, FFeedbackContext *Warn, bool &bOutOperationCanceled) override; + + virtual bool CanReimport(UObject *Obj, TArray &OutFilenames) override; + virtual void SetReimportPaths(UObject *Obj, const TArray &NewReimportPaths) override; + virtual EReimportResult::Type Reimport(UObject *Obj) override; +}; diff --git a/Plugins/SpinePlugin/Source/SpineEditorPlugin/SpineEditorPlugin.Build.cs b/Plugins/SpinePlugin/Source/SpineEditorPlugin/SpineEditorPlugin.Build.cs new file mode 100644 index 0000000..364a720 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpineEditorPlugin/SpineEditorPlugin.Build.cs @@ -0,0 +1,36 @@ +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class SpineEditorPlugin : ModuleRules + { + public SpineEditorPlugin(ReadOnlyTargetRules target) : base(target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "../SpinePlugin/Public/spine-cpp/include")); + + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "Private")); + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "../SpinePlugin/Public/spine-cpp/include")); + + PublicDependencyModuleNames.AddRange(new [] { + "Core", + "CoreUObject", + "Engine", + "UnrealEd", + "SpinePlugin" + }); + + PublicIncludePathModuleNames.AddRange(new [] { + "AssetTools", + "AssetRegistry" + }); + + DynamicallyLoadedModuleNames.AddRange(new [] { + "AssetTools", + "AssetRegistry" + }); + } + } +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SSpineWidget.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SSpineWidget.cpp new file mode 100644 index 0000000..585291c --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SSpineWidget.cpp @@ -0,0 +1,415 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SSpineWidget.h" +#include "Framework/Application/SlateApplication.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Materials/MaterialInterface.h" +#include "Rendering/DrawElements.h" +#include "Runtime/SlateRHIRenderer/Public/Interfaces/ISlateRHIRendererModule.h" +#include "Slate/SMeshWidget.h" +#include "Slate/SlateVectorArtData.h" +#include "SlateMaterialBrush.h" +#include "SpineWidget.h" +#include + +using namespace spine; + +static int brushNameId = 0; + +// Workaround for https://github.com/EsotericSoftware/spine-runtimes/issues/1458 +// See issue comments for more information. +struct SpineSlateMaterialBrush : public FSlateBrush { + static TArray NamePool; + static FCriticalSection NamePoolLock; + + SpineSlateMaterialBrush(class UMaterialInterface &InMaterial, const FVector2D &InImageSize) + : FSlateBrush(ESlateBrushDrawType::Image, FName(TEXT("None")), FMargin(0), ESlateBrushTileType::NoTile, ESlateBrushImageType::FullColor, InImageSize, FLinearColor::White, &InMaterial) { + // Workaround for https://github.com/EsotericSoftware/spine-runtimes/issues/2006 + FScopeLock Lock(&NamePoolLock); + + if (NamePool.Num() > 0) { + ResourceName = NamePool.Pop(false); + } else { + static uint32 NextId = 0; + FString brushName = TEXT("SpineSlateMatBrush"); + brushName.AppendInt(NextId++); + ResourceName = FName(*brushName); + } + } + + ~SpineSlateMaterialBrush() { + FScopeLock Lock(&NamePoolLock); + NamePool.Add(ResourceName); + } +}; + +TArray SpineSlateMaterialBrush::NamePool; +FCriticalSection SpineSlateMaterialBrush::NamePoolLock; + +void SSpineWidget::Construct(const FArguments &args) { +} + +void SSpineWidget::SetData(USpineWidget *Widget) { + this->widget = Widget; + if (widget && widget->skeleton && widget->Atlas) { + Skeleton *skeleton = widget->skeleton; + skeleton->setToSetupPose(); + skeleton->updateWorldTransform(Physics_None); + Vector scratchBuffer; + float x, y, w, h; + skeleton->getBounds(x, y, w, h, scratchBuffer); + boundsMin.X = x; + boundsMin.Y = y; + boundsSize.X = w; + boundsSize.Y = h; + } +} + +static void setVertex(FSlateVertex *vertex, float x, float y, float u, float v, const FColor &color, const FVector2D &offset) { + vertex->Position.X = offset.X + x; + vertex->Position.Y = offset.Y + y; + vertex->TexCoords[0] = u; + vertex->TexCoords[1] = v; + vertex->TexCoords[2] = u; + vertex->TexCoords[3] = v; + vertex->MaterialTexCoords.X = u; + vertex->MaterialTexCoords.Y = v; + vertex->Color = color; + vertex->PixelSize[0] = 1; + vertex->PixelSize[1] = 1; +} + +int32 SSpineWidget::OnPaint(const FPaintArgs &Args, const FGeometry &AllottedGeometry, const FSlateRect &MyClippingRect, FSlateWindowElementList &OutDrawElements, + int32 LayerId, const FWidgetStyle &InWidgetStyle, bool bParentEnabled) const { + + SSpineWidget *self = (SSpineWidget *) this; + UMaterialInstanceDynamic *MatNow = nullptr; + + if (widget && widget->skeleton && widget->Atlas) { + widget->skeleton->getColor().set(widget->Color.R, widget->Color.G, widget->Color.B, widget->Color.A); + + if (widget->atlasNormalBlendMaterials.Num() != widget->Atlas->atlasPages.Num()) { + widget->atlasNormalBlendMaterials.SetNum(0); + widget->pageToNormalBlendMaterial.Empty(); + widget->atlasAdditiveBlendMaterials.SetNum(0); + widget->pageToAdditiveBlendMaterial.Empty(); + widget->atlasMultiplyBlendMaterials.SetNum(0); + widget->pageToMultiplyBlendMaterial.Empty(); + widget->atlasScreenBlendMaterials.SetNum(0); + widget->pageToScreenBlendMaterial.Empty(); + + for (int i = 0; i < widget->Atlas->atlasPages.Num(); i++) { + AtlasPage *currPage = widget->Atlas->GetAtlas()->getPages()[i]; + + UMaterialInstanceDynamic *material = UMaterialInstanceDynamic::Create(widget->NormalBlendMaterial, widget); + material->SetTextureParameterValue(widget->TextureParameterName, widget->Atlas->atlasPages[i]); + widget->atlasNormalBlendMaterials.Add(material); + widget->pageToNormalBlendMaterial.Add(currPage, material); + + material = UMaterialInstanceDynamic::Create(widget->AdditiveBlendMaterial, widget); + material->SetTextureParameterValue(widget->TextureParameterName, widget->Atlas->atlasPages[i]); + widget->atlasAdditiveBlendMaterials.Add(material); + widget->pageToAdditiveBlendMaterial.Add(currPage, material); + + material = UMaterialInstanceDynamic::Create(widget->MultiplyBlendMaterial, widget); + material->SetTextureParameterValue(widget->TextureParameterName, widget->Atlas->atlasPages[i]); + widget->atlasMultiplyBlendMaterials.Add(material); + widget->pageToMultiplyBlendMaterial.Add(currPage, material); + + material = UMaterialInstanceDynamic::Create(widget->ScreenBlendMaterial, widget); + material->SetTextureParameterValue(widget->TextureParameterName, widget->Atlas->atlasPages[i]); + widget->atlasScreenBlendMaterials.Add(material); + widget->pageToScreenBlendMaterial.Add(currPage, material); + } + } else { + widget->pageToNormalBlendMaterial.Empty(); + widget->pageToAdditiveBlendMaterial.Empty(); + widget->pageToMultiplyBlendMaterial.Empty(); + widget->pageToScreenBlendMaterial.Empty(); + + for (int i = 0; i < widget->Atlas->atlasPages.Num(); i++) { + AtlasPage *currPage = widget->Atlas->GetAtlas()->getPages()[i]; + + UTexture2D *texture = widget->Atlas->atlasPages[i]; + UTexture *oldTexture = nullptr; + + UMaterialInstanceDynamic *current = widget->atlasNormalBlendMaterials[i]; + if (!current || !current->GetTextureParameterValue(widget->TextureParameterName, oldTexture) || oldTexture != texture) { + UMaterialInstanceDynamic *material = UMaterialInstanceDynamic::Create(widget->NormalBlendMaterial, widget); + material->SetTextureParameterValue(widget->TextureParameterName, texture); + widget->atlasNormalBlendMaterials[i] = material; + } + widget->pageToNormalBlendMaterial.Add(currPage, widget->atlasNormalBlendMaterials[i]); + + current = widget->atlasAdditiveBlendMaterials[i]; + if (!current || !current->GetTextureParameterValue(widget->TextureParameterName, oldTexture) || oldTexture != texture) { + UMaterialInstanceDynamic *material = UMaterialInstanceDynamic::Create(widget->AdditiveBlendMaterial, widget); + material->SetTextureParameterValue(widget->TextureParameterName, texture); + widget->atlasAdditiveBlendMaterials[i] = material; + } + widget->pageToAdditiveBlendMaterial.Add(currPage, widget->atlasAdditiveBlendMaterials[i]); + + current = widget->atlasMultiplyBlendMaterials[i]; + if (!current || !current->GetTextureParameterValue(widget->TextureParameterName, oldTexture) || oldTexture != texture) { + UMaterialInstanceDynamic *material = UMaterialInstanceDynamic::Create(widget->MultiplyBlendMaterial, widget); + material->SetTextureParameterValue(widget->TextureParameterName, texture); + widget->atlasMultiplyBlendMaterials[i] = material; + } + widget->pageToMultiplyBlendMaterial.Add(currPage, widget->atlasMultiplyBlendMaterials[i]); + + current = widget->atlasScreenBlendMaterials[i]; + if (!current || !current->GetTextureParameterValue(widget->TextureParameterName, oldTexture) || oldTexture != texture) { + UMaterialInstanceDynamic *material = UMaterialInstanceDynamic::Create(widget->ScreenBlendMaterial, widget); + material->SetTextureParameterValue(widget->TextureParameterName, texture); + widget->atlasScreenBlendMaterials[i] = material; + } + widget->pageToScreenBlendMaterial.Add(currPage, widget->atlasScreenBlendMaterials[i]); + } + } + + self->UpdateMesh(LayerId, OutDrawElements, AllottedGeometry, widget->skeleton); + } + + return LayerId; +} + +void SSpineWidget::Flush(int32 LayerId, FSlateWindowElementList &OutDrawElements, const FGeometry &AllottedGeometry, int &Idx, TArray &Vertices, TArray &Indices, TArray &Uvs, TArray &Colors, TArray &Colors2, UMaterialInstanceDynamic *Material) { + if (Vertices.Num() == 0) return; + SSpineWidget *self = (SSpineWidget *) this; + + const FVector2D widgetSize = AllottedGeometry.GetLocalSize(); + const FVector2D sizeScale = widgetSize / FVector2D(boundsSize.X, boundsSize.Y); + const float setupScale = sizeScale.GetMin(); + + for (int i = 0; i < Vertices.Num(); i++) { + Vertices[i] = (Vertices[i] + FVector(-boundsMin.X - boundsSize.X / 2, boundsMin.Y + boundsSize.Y / 2, 0)) * setupScale + FVector(widgetSize.X / 2, widgetSize.Y / 2, 0); + } + + self->renderData.IndexData.SetNumUninitialized(Indices.Num()); + SlateIndex *indexData = (SlateIndex *) renderData.IndexData.GetData(); + for (int i = 0; i < Indices.Num(); i++) { + indexData[i] = (SlateIndex) Indices[i]; + } + + self->renderData.VertexData.SetNumUninitialized(Vertices.Num()); + FSlateVertex *vertexData = (FSlateVertex *) renderData.VertexData.GetData(); + FVector2D offset = AllottedGeometry.GetAbsolutePositionAtCoordinates(FVector2D(0.0f, 0.0f)); + FColor white = FColor(0xffffffff); + const FSlateRenderTransform &Transform = AllottedGeometry.GetAccumulatedRenderTransform(); + + for (size_t i = 0; i < (size_t) Vertices.Num(); i++) { + setVertex(&vertexData[i], 0, 0, Uvs[i].X, Uvs[i].Y, Colors[i], Transform.TransformPoint(FVector2D(Vertices[i]))); + } + + brush = &widget->Brush; + if (Material) { + renderData.Brush = MakeShareable(new SpineSlateMaterialBrush(*Material, FVector2D(64, 64))); + renderData.RenderingResourceHandle = FSlateApplication::Get().GetRenderer()->GetResourceHandle(*renderData.Brush); + } + + if (renderData.RenderingResourceHandle.IsValid()) { + FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, renderData.RenderingResourceHandle, renderData.VertexData, renderData.IndexData, nullptr, 0, 0); + } + + Vertices.SetNum(0); + Indices.SetNum(0); + Uvs.SetNum(0); + Colors.SetNum(0); + Colors2.SetNum(0); + Idx++; +} + +FVector2D SSpineWidget::ComputeDesiredSize(float X) const { + if (widget && widget->skeleton && widget->Atlas) { + return FVector2D(boundsSize.X, boundsSize.Y); + } else { + return FVector2D(256, 256); + } +} + +void SSpineWidget::UpdateMesh(int32 LayerId, FSlateWindowElementList &OutDrawElements, const FGeometry &AllottedGeometry, Skeleton *Skeleton) { + TArray vertices; + TArray indices; + TArray uvs; + TArray colors; + TArray darkColors; + + int idx = 0; + int meshSection = 0; + UMaterialInstanceDynamic *lastMaterial = nullptr; + + SkeletonClipping &clipper = widget->clipper; + Vector &worldVertices = widget->worldVertices; + + float depthOffset = 0; + unsigned short quadIndices[] = {0, 1, 2, 0, 2, 3}; + + for (int i = 0; i < (int) Skeleton->getSlots().size(); ++i) { + Vector *attachmentVertices = &worldVertices; + unsigned short *attachmentIndices = nullptr; + int numVertices; + int numIndices; + AtlasRegion *attachmentAtlasRegion = nullptr; + Color attachmentColor; + attachmentColor.set(1, 1, 1, 1); + float *attachmentUvs = nullptr; + + Slot *slot = Skeleton->getDrawOrder()[i]; + if (!slot->getBone().isActive()) { + clipper.clipEnd(*slot); + continue; + } + + Attachment *attachment = slot->getAttachment(); + if (!attachment) { + clipper.clipEnd(*slot); + continue; + } + if (!attachment->getRTTI().isExactly(RegionAttachment::rtti) && !attachment->getRTTI().isExactly(MeshAttachment::rtti) && !attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { + clipper.clipEnd(*slot); + continue; + } + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = (RegionAttachment *) attachment; + attachmentColor.set(regionAttachment->getColor()); + attachmentVertices->setSize(8, 0); + regionAttachment->computeWorldVertices(*slot, *attachmentVertices, 0, 2); + attachmentAtlasRegion = (AtlasRegion *) regionAttachment->getRegion(); + attachmentIndices = quadIndices; + attachmentUvs = regionAttachment->getUVs().buffer(); + numVertices = 4; + numIndices = 6; + } else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + MeshAttachment *mesh = (MeshAttachment *) attachment; + attachmentColor.set(mesh->getColor()); + attachmentVertices->setSize(mesh->getWorldVerticesLength(), 0); + mesh->computeWorldVertices(*slot, 0, mesh->getWorldVerticesLength(), attachmentVertices->buffer(), 0, 2); + attachmentAtlasRegion = (AtlasRegion *) mesh->getRegion(); + attachmentIndices = mesh->getTriangles().buffer(); + attachmentUvs = mesh->getUVs().buffer(); + numVertices = mesh->getWorldVerticesLength() >> 1; + numIndices = mesh->getTriangles().size(); + } else /* clipping */ { + ClippingAttachment *clip = (ClippingAttachment *) attachment; + clipper.clipStart(*slot, clip); + continue; + } + + // if the user switches the atlas data while not having switched + // to the correct skeleton data yet, we won't find any regions. + // ignore regions for which we can't find a material + UMaterialInstanceDynamic *material = nullptr; + switch (slot->getData().getBlendMode()) { + case BlendMode_Normal: + if (!widget->pageToNormalBlendMaterial.Contains(attachmentAtlasRegion->page)) { + clipper.clipEnd(*slot); + continue; + } + material = widget->pageToNormalBlendMaterial[attachmentAtlasRegion->page]; + break; + case BlendMode_Additive: + if (!widget->pageToAdditiveBlendMaterial.Contains(attachmentAtlasRegion->page)) { + clipper.clipEnd(*slot); + continue; + } + material = widget->pageToAdditiveBlendMaterial[attachmentAtlasRegion->page]; + break; + case BlendMode_Multiply: + if (!widget->pageToMultiplyBlendMaterial.Contains(attachmentAtlasRegion->page)) { + clipper.clipEnd(*slot); + continue; + } + material = widget->pageToMultiplyBlendMaterial[attachmentAtlasRegion->page]; + break; + case BlendMode_Screen: + if (!widget->pageToScreenBlendMaterial.Contains(attachmentAtlasRegion->page)) { + clipper.clipEnd(*slot); + continue; + } + material = widget->pageToScreenBlendMaterial[attachmentAtlasRegion->page]; + break; + default: + if (!widget->pageToNormalBlendMaterial.Contains(attachmentAtlasRegion->page)) { + clipper.clipEnd(*slot); + continue; + } + material = widget->pageToNormalBlendMaterial[attachmentAtlasRegion->page]; + } + + if (clipper.isClipping()) { + clipper.clipTriangles(attachmentVertices->buffer(), attachmentIndices, numIndices, attachmentUvs, 2); + attachmentVertices = &clipper.getClippedVertices(); + numVertices = clipper.getClippedVertices().size() >> 1; + attachmentIndices = clipper.getClippedTriangles().buffer(); + numIndices = clipper.getClippedTriangles().size(); + attachmentUvs = clipper.getClippedUVs().buffer(); + if (clipper.getClippedTriangles().size() == 0) { + clipper.clipEnd(*slot); + continue; + } + } + + if (lastMaterial != material) { + Flush(LayerId, OutDrawElements, AllottedGeometry, meshSection, vertices, indices, uvs, colors, darkColors, lastMaterial); + lastMaterial = material; + idx = 0; + } + + uint8 r = static_cast(Skeleton->getColor().r * slot->getColor().r * attachmentColor.r * 255); + uint8 g = static_cast(Skeleton->getColor().g * slot->getColor().g * attachmentColor.g * 255); + uint8 b = static_cast(Skeleton->getColor().b * slot->getColor().b * attachmentColor.b * 255); + uint8 a = static_cast(Skeleton->getColor().a * slot->getColor().a * attachmentColor.a * 255); + + float dr = slot->hasDarkColor() ? slot->getDarkColor().r : 0.0f; + float dg = slot->hasDarkColor() ? slot->getDarkColor().g : 0.0f; + float db = slot->hasDarkColor() ? slot->getDarkColor().b : 0.0f; + + float *verticesPtr = attachmentVertices->buffer(); + for (int j = 0; j < numVertices << 1; j += 2) { + colors.Add(FColor(r, g, b, a)); + darkColors.Add(FVector(dr, dg, db)); + vertices.Add(FVector(verticesPtr[j], -verticesPtr[j + 1], depthOffset)); + uvs.Add(FVector2D(attachmentUvs[j], attachmentUvs[j + 1])); + } + + for (int j = 0; j < numIndices; j++) { + indices.Add(idx + attachmentIndices[j]); + } + + idx += numVertices; + depthOffset += widget->DepthOffset; + + clipper.clipEnd(*slot); + } + + Flush(LayerId, OutDrawElements, AllottedGeometry, meshSection, vertices, indices, uvs, colors, darkColors, lastMaterial); + clipper.clipEnd(); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineAtlasAsset.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineAtlasAsset.cpp new file mode 100644 index 0000000..0971e52 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineAtlasAsset.cpp @@ -0,0 +1,124 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineAtlasAsset.h" +#include "spine/spine.h" +#include +#include + +#include "EditorFramework/AssetImportData.h" + +#define LOCTEXT_NAMESPACE "Spine" + +using namespace spine; + +#if WITH_EDITORONLY_DATA + +void USpineAtlasAsset::SetAtlasFileName(const FName &AtlasFileName) { + importData->UpdateFilenameOnly(AtlasFileName.ToString()); + TArray files; + importData->ExtractFilenames(files); + if (files.Num() > 0) + atlasFileName = FName(*files[0]); +} + +void USpineAtlasAsset::PostInitProperties() { + if (!HasAnyFlags(RF_ClassDefaultObject)) + importData = NewObject(this, TEXT("AssetImportData")); + Super::PostInitProperties(); +} + +void USpineAtlasAsset::Serialize(FArchive &Ar) { + Super::Serialize(Ar); +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION <= 27 + if (Ar.IsLoading() && Ar.UE4Ver() < VER_UE4_ASSET_IMPORT_DATA_AS_JSON && !importData) +#else + if (Ar.IsLoading() && Ar.UEVer() < VER_UE4_ASSET_IMPORT_DATA_AS_JSON && !importData) +#endif + importData = NewObject(this, TEXT("AssetImportData")); +} + +#endif + +FName USpineAtlasAsset::GetAtlasFileName() const { +#if WITH_EDITORONLY_DATA + TArray files; + if (importData) + importData->ExtractFilenames(files); + if (files.Num() > 0) + return FName(*files[0]); + else + return atlasFileName; +#else + return atlasFileName; +#endif +} + +void USpineAtlasAsset::SetRawData(const FString &RawData) { + this->rawData = RawData; + if (atlas) { + delete atlas; + atlas = nullptr; + } +} + +void USpineAtlasAsset::BeginDestroy() { + if (atlas) { + delete atlas; + atlas = nullptr; + } + Super::BeginDestroy(); +} + +class UETextureLoader : public TextureLoader { + void load(AtlasPage &page, const String &path) { + page.texture = (void *) (uintptr_t) page.index; + } + + void unload(void *texture) { + } +}; + +UETextureLoader _spineUETextureLoader; + +Atlas *USpineAtlasAsset::GetAtlas() { + if (!atlas) { + if (atlas) { + delete atlas; + atlas = nullptr; + } + std::string t = TCHAR_TO_UTF8(*rawData); + + atlas = new (__FILE__, __LINE__) + Atlas(t.c_str(), strlen(t.c_str()), "", &_spineUETextureLoader); + } + return this->atlas; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineBoneDriverComponent.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineBoneDriverComponent.cpp new file mode 100644 index 0000000..830786e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineBoneDriverComponent.cpp @@ -0,0 +1,67 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineBoneDriverComponent.h" +#include "SpineSkeletonComponent.h" +#include "GameFramework/Actor.h" + +USpineBoneDriverComponent::USpineBoneDriverComponent() { + PrimaryComponentTick.bCanEverTick = true; + bTickInEditor = true; + bAutoActivate = true; +} + +void USpineBoneDriverComponent::BeginPlay() { + Super::BeginPlay(); +} + +void USpineBoneDriverComponent::BeforeUpdateWorldTransform(USpineSkeletonComponent *skeleton) { + if (skeleton == lastBoundComponent && skeleton) { + if (UseComponentTransform) { + skeleton->SetBoneWorldPosition(BoneName, GetComponentLocation()); + } else { + AActor *owner = GetOwner(); + if (owner) skeleton->SetBoneWorldPosition(BoneName, owner->GetActorLocation()); + } + } +} + +void USpineBoneDriverComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (Target) { + USpineSkeletonComponent *skeleton = static_cast(Target->GetComponentByClass(USpineSkeletonComponent::StaticClass())); + if (skeleton != lastBoundComponent && skeleton) { + // if (lastBoundComponent) lastBoundComponent->BeforeUpdateWorldTransform.RemoveAll(this); + if (!skeleton->BeforeUpdateWorldTransform.GetAllObjects().Contains(this)) + skeleton->BeforeUpdateWorldTransform.AddDynamic(this, &USpineBoneDriverComponent::BeforeUpdateWorldTransform); + lastBoundComponent = skeleton; + } + } +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineBoneFollowerComponent.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineBoneFollowerComponent.cpp new file mode 100644 index 0000000..cbbb8de --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineBoneFollowerComponent.cpp @@ -0,0 +1,65 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineBoneFollowerComponent.h" +#include "SpineSkeletonComponent.h" +#include "GameFramework/Actor.h" + +USpineBoneFollowerComponent::USpineBoneFollowerComponent() { + PrimaryComponentTick.bCanEverTick = true; + bTickInEditor = true; + bAutoActivate = true; +} + +void USpineBoneFollowerComponent::BeginPlay() { + Super::BeginPlay(); +} + +void USpineBoneFollowerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (Target) { + USpineSkeletonComponent *skeleton = static_cast(Target->GetComponentByClass(USpineSkeletonComponent::StaticClass())); + if (skeleton) { + FTransform transform = skeleton->GetBoneWorldTransform(BoneName); + if (UseComponentTransform) { + if (UsePosition) SetWorldLocation(transform.GetLocation()); + if (UseRotation) SetWorldRotation(transform.GetRotation()); + if (UseScale) SetWorldScale3D(transform.GetScale3D()); + } else { + AActor *owner = GetOwner(); + if (owner) { + if (UsePosition) owner->SetActorLocation(transform.GetLocation()); + if (UseRotation) owner->SetActorRotation(transform.GetRotation()); + if (UseScale) owner->SetActorScale3D(transform.GetScale3D()); + } + } + } + } +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpinePlugin.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpinePlugin.cpp new file mode 100644 index 0000000..c7d66ec --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpinePlugin.cpp @@ -0,0 +1,74 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpinePlugin.h" +#include "spine/Extension.h" + +DEFINE_LOG_CATEGORY(SpineLog); + +class FSpinePlugin : public SpinePlugin { + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; + +IMPLEMENT_MODULE(FSpinePlugin, SpinePlugin) + +void FSpinePlugin::StartupModule() { +} + +void FSpinePlugin::ShutdownModule() {} + +class Ue4Extension : public spine::DefaultSpineExtension { +public: + Ue4Extension() : spine::DefaultSpineExtension() {} + + virtual ~Ue4Extension() {} + + virtual void *_alloc(size_t size, const char *file, int line) { + return FMemory::Malloc(size); + } + + virtual void *_calloc(size_t size, const char *file, int line) { + void *result = FMemory::Malloc(size); + FMemory::Memset(result, 0, size); + return result; + } + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) { + return FMemory::Realloc(ptr, size); + } + + virtual void _free(void *mem, const char *file, int line) { + FMemory::Free(mem); + } +}; + +spine::SpineExtension *spine::getDefaultExtension() { + return new Ue4Extension(); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonAnimationComponent.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonAnimationComponent.cpp new file mode 100644 index 0000000..5d7033c --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonAnimationComponent.cpp @@ -0,0 +1,314 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineSkeletonAnimationComponent.h" +#include "SpineAtlasAsset.h" + +#define LOCTEXT_NAMESPACE "Spine" + +using namespace spine; + +void UTrackEntry::SetTrackEntry(TrackEntry *trackEntry) { + if (entry) entry->setRendererObject(nullptr); + this->entry = trackEntry; + if (entry) entry->setRendererObject((void *) this); +} + +void callback(AnimationState *state, spine::EventType type, TrackEntry *entry, Event *event) { + USpineSkeletonAnimationComponent *component = (USpineSkeletonAnimationComponent *) state->getRendererObject(); + + if (entry->getRendererObject()) { + UTrackEntry *uEntry = (UTrackEntry *) entry->getRendererObject(); + if (type == EventType_Start) { + component->AnimationStart.Broadcast(uEntry); + uEntry->AnimationStart.Broadcast(uEntry); + } else if (type == EventType_Interrupt) { + component->AnimationInterrupt.Broadcast(uEntry); + uEntry->AnimationInterrupt.Broadcast(uEntry); + } else if (type == EventType_Event) { + FSpineEvent evt; + evt.SetEvent(event); + component->AnimationEvent.Broadcast(uEntry, evt); + uEntry->AnimationEvent.Broadcast(uEntry, evt); + } else if (type == EventType_Complete) { + component->AnimationComplete.Broadcast(uEntry); + uEntry->AnimationComplete.Broadcast(uEntry); + } else if (type == EventType_End) { + component->AnimationEnd.Broadcast(uEntry); + uEntry->AnimationEnd.Broadcast(uEntry); + } else if (type == EventType_Dispose) { + component->AnimationDispose.Broadcast(uEntry); + uEntry->AnimationDispose.Broadcast(uEntry); + uEntry->SetTrackEntry(nullptr); + component->GCTrackEntry(uEntry); + } + } +} + +USpineSkeletonAnimationComponent::USpineSkeletonAnimationComponent() { + PrimaryComponentTick.bCanEverTick = true; + bTickInEditor = true; + bAutoActivate = true; + bAutoPlaying = true; + physicsTimeScale = 1; +} + +void USpineSkeletonAnimationComponent::BeginPlay() { + Super::BeginPlay(); + for (UTrackEntry *entry : trackEntries) { + if (entry && entry->GetTrackEntry()) { + entry->GetTrackEntry()->setRendererObject(nullptr); + } + } + trackEntries.Empty(); +} + +void UTrackEntry::BeginDestroy() { + Super::BeginDestroy(); +} + +void USpineSkeletonAnimationComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { + Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + InternalTick(DeltaTime, true, TickType == LEVELTICK_ViewportsOnly); +} + +void USpineSkeletonAnimationComponent::InternalTick(float DeltaTime, bool CallDelegates, bool Preview) { + CheckState(); + + if (state && bAutoPlaying) { + if (Preview) { + if (lastPreviewAnimation != PreviewAnimation) { + if (PreviewAnimation != "") SetAnimation(0, PreviewAnimation, true); + else + SetEmptyAnimation(0, 0); + lastPreviewAnimation = PreviewAnimation; + } + + if (lastPreviewSkin != PreviewSkin) { + if (PreviewSkin != "") SetSkin(PreviewSkin); + else + SetSkin("default"); + lastPreviewSkin = PreviewSkin; + } + } + state->update(DeltaTime); + state->apply(*skeleton); + if (CallDelegates) BeforeUpdateWorldTransform.Broadcast(this); + skeleton->update(physicsTimeScale * DeltaTime); + skeleton->updateWorldTransform(Physics_Update); + if (CallDelegates) AfterUpdateWorldTransform.Broadcast(this); + } +} + +void USpineSkeletonAnimationComponent::CheckState() { + bool needsUpdate = lastAtlas != Atlas || lastData != SkeletonData; + + if (!needsUpdate) { + // Are we doing a re-import? Then check if the underlying spine-cpp data + // has changed. + if (lastAtlas && lastAtlas == Atlas && lastData && lastData == SkeletonData) { + spine::Atlas *atlas = Atlas->GetAtlas(); + if (lastSpineAtlas != atlas) { + needsUpdate = true; + } + if (skeleton && skeleton->getData() != SkeletonData->GetSkeletonData(atlas)) { + needsUpdate = true; + } + } + } + + if (needsUpdate) { + DisposeState(); + + if (Atlas && SkeletonData) { + spine::SkeletonData *data = SkeletonData->GetSkeletonData(Atlas->GetAtlas()); + if (data) { + skeleton = new (__FILE__, __LINE__) Skeleton(data); + AnimationStateData *stateData = SkeletonData->GetAnimationStateData(Atlas->GetAtlas()); + state = new (__FILE__, __LINE__) AnimationState(stateData); + state->setRendererObject((void *) this); + state->setListener(callback); + trackEntries.Empty(); + } + } + + lastAtlas = Atlas; + lastSpineAtlas = Atlas ? Atlas->GetAtlas() : nullptr; + lastData = SkeletonData; + } +} + +void USpineSkeletonAnimationComponent::DisposeState() { + if (state) { + delete state; + state = nullptr; + } + + if (skeleton) { + delete skeleton; + skeleton = nullptr; + } + + trackEntries.Empty(); +} + +void USpineSkeletonAnimationComponent::FinishDestroy() { + DisposeState(); + Super::FinishDestroy(); +} + +void USpineSkeletonAnimationComponent::SetAutoPlay(bool bInAutoPlays) { + bAutoPlaying = bInAutoPlays; +} + +void USpineSkeletonAnimationComponent::SetPlaybackTime(float InPlaybackTime, bool bCallDelegates) { + CheckState(); + + if (state && state->getCurrent(0)) { + spine::Animation *CurrentAnimation = state->getCurrent(0)->getAnimation(); + const float CurrentTime = state->getCurrent(0)->getTrackTime(); + InPlaybackTime = FMath::Clamp(InPlaybackTime, 0.0f, CurrentAnimation->getDuration()); + const float DeltaTime = InPlaybackTime - CurrentTime; + state->update(DeltaTime); + state->apply(*skeleton); + + //Call delegates and perform the world transform + if (bCallDelegates) { + BeforeUpdateWorldTransform.Broadcast(this); + } + skeleton->updateWorldTransform(Physics_Update); + if (bCallDelegates) { + AfterUpdateWorldTransform.Broadcast(this); + } + } +} + +void USpineSkeletonAnimationComponent::SetTimeScale(float timeScale) { + CheckState(); + if (state) state->setTimeScale(timeScale); +} + +float USpineSkeletonAnimationComponent::GetTimeScale() { + CheckState(); + if (state) return state->getTimeScale(); + return 1; +} + +UTrackEntry *USpineSkeletonAnimationComponent::SetAnimation(int trackIndex, FString animationName, bool loop) { + CheckState(); + if (state && skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*animationName))) { + state->disableQueue(); + TrackEntry *entry = state->setAnimation(trackIndex, TCHAR_TO_UTF8(*animationName), loop); + state->enableQueue(); + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } else + return NewObject(); +} + +UTrackEntry *USpineSkeletonAnimationComponent::AddAnimation(int trackIndex, FString animationName, bool loop, float delay) { + CheckState(); + if (state && skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*animationName))) { + state->disableQueue(); + TrackEntry *entry = state->addAnimation(trackIndex, TCHAR_TO_UTF8(*animationName), loop, delay); + state->enableQueue(); + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } else + return NewObject(); +} + +UTrackEntry *USpineSkeletonAnimationComponent::SetEmptyAnimation(int trackIndex, float mixDuration) { + CheckState(); + if (state) { + TrackEntry *entry = state->setEmptyAnimation(trackIndex, mixDuration); + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } else + return NewObject(); +} + +UTrackEntry *USpineSkeletonAnimationComponent::AddEmptyAnimation(int trackIndex, float mixDuration, float delay) { + CheckState(); + if (state) { + TrackEntry *entry = state->addEmptyAnimation(trackIndex, mixDuration, delay); + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } else + return NewObject(); +} + +UTrackEntry *USpineSkeletonAnimationComponent::GetCurrent(int trackIndex) { + CheckState(); + if (state && state->getCurrent(trackIndex)) { + TrackEntry *entry = state->getCurrent(trackIndex); + if (entry->getRendererObject()) { + return (UTrackEntry *) entry->getRendererObject(); + } else { + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } + } else + return NewObject(); +} + +void USpineSkeletonAnimationComponent::ClearTracks() { + CheckState(); + if (state) { + state->clearTracks(); + } +} + +void USpineSkeletonAnimationComponent::ClearTrack(int trackIndex) { + CheckState(); + if (state) { + state->clearTrack(trackIndex); + } +} + +void USpineSkeletonAnimationComponent::SetPhysicsTimeScale(float scale) { + physicsTimeScale = scale; +} + +float USpineSkeletonAnimationComponent::GetPhysicsTimeScale() { + return physicsTimeScale; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonComponent.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonComponent.cpp new file mode 100644 index 0000000..513702f --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonComponent.cpp @@ -0,0 +1,376 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineSkeletonComponent.h" +#include "SpineSkeletonRendererComponent.h" +#include "SpineAtlasAsset.h" + +#include "spine/spine.h" + +#define LOCTEXT_NAMESPACE "Spine" + +using namespace spine; + +USpineSkeletonComponent::USpineSkeletonComponent() { + PrimaryComponentTick.bCanEverTick = true; + bTickInEditor = true; + bAutoActivate = true; +} + +bool USpineSkeletonComponent::SetSkins(UPARAM(ref) TArray &SkinNames) { + CheckState(); + if (skeleton) { + spine::Skin *newSkin = new spine::Skin("__spine-ue3_custom_skin"); + for (auto &skinName : SkinNames) { + spine::Skin *skin = skeleton->getData()->findSkin(TCHAR_TO_UTF8(*skinName)); + if (!skin) { + delete newSkin; + return false; + } + newSkin->addSkin(skin); + } + skeleton->setSkin(newSkin); + if (customSkin != nullptr) { + delete customSkin; + } + customSkin = newSkin; + return true; + } else + return false; +} + +bool USpineSkeletonComponent::SetSkin(const FString skinName) { + CheckState(); + if (skeleton) { + Skin *skin = skeleton->getData()->findSkin(TCHAR_TO_UTF8(*skinName)); + if (!skin) return false; + skeleton->setSkin(skin); + return true; + } else + return false; +} + +void USpineSkeletonComponent::GetSkins(TArray &Skins) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getData()->getSkins().size(); i < n; i++) { + Skins.Add(skeleton->getData()->getSkins()[i]->getName().buffer()); + } + } +} + +bool USpineSkeletonComponent::HasSkin(const FString skinName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findSkin(TCHAR_TO_UTF8(*skinName)) != nullptr; + } + return false; +} + +bool USpineSkeletonComponent::SetAttachment(const FString slotName, const FString attachmentName) { + CheckState(); + if (skeleton) { + if (attachmentName.IsEmpty()) { + skeleton->setAttachment(TCHAR_TO_UTF8(*slotName), NULL); + return true; + } + if (!skeleton->getAttachment(TCHAR_TO_UTF8(*slotName), TCHAR_TO_UTF8(*attachmentName))) return false; + skeleton->setAttachment(TCHAR_TO_UTF8(*slotName), TCHAR_TO_UTF8(*attachmentName)); + return true; + } + return false; +} + +FTransform USpineSkeletonComponent::GetBoneWorldTransform(const FString &BoneName) { + CheckState(); + if (skeleton) { + Bone *bone = skeleton->findBone(TCHAR_TO_UTF8(*BoneName)); + if (!bone) return FTransform(); + + // Need to fetch the renderer component to get world transform of actor plus + // offset by renderer component and its parent component(s). If no renderer + // component is found, this components owner's transform is used as a fallback + FTransform baseTransform; + AActor *owner = GetOwner(); + if (owner) { + USpineSkeletonRendererComponent *rendererComponent = static_cast(owner->GetComponentByClass(USpineSkeletonRendererComponent::StaticClass())); + if (rendererComponent) baseTransform = rendererComponent->GetComponentTransform(); + else + baseTransform = owner->GetActorTransform(); + } + + FVector position(bone->getWorldX(), 0, bone->getWorldY()); + FMatrix localTransform; + localTransform.SetIdentity(); + localTransform.SetAxis(2, FVector(bone->getA(), 0, bone->getC())); + localTransform.SetAxis(0, FVector(bone->getB(), 0, bone->getD())); + localTransform.SetOrigin(FVector(bone->getWorldX(), 0, bone->getWorldY())); + localTransform = localTransform * baseTransform.ToMatrixWithScale(); + + FTransform result; + result.SetFromMatrix(localTransform); + return result; + } + return FTransform(); +} + +void USpineSkeletonComponent::SetBoneWorldPosition(const FString &BoneName, const FVector &position) { + CheckState(); + if (skeleton) { + Bone *bone = skeleton->findBone(TCHAR_TO_UTF8(*BoneName)); + if (!bone) return; + + // Need to fetch the renderer component to get world transform of actor plus + // offset by renderer component and its parent component(s). If no renderer + // component is found, this components owner's transform is used as a fallback + FTransform baseTransform; + AActor *owner = GetOwner(); + if (owner) { + USpineSkeletonRendererComponent *rendererComponent = static_cast(owner->GetComponentByClass(USpineSkeletonRendererComponent::StaticClass())); + if (rendererComponent) baseTransform = rendererComponent->GetComponentTransform(); + else + baseTransform = owner->GetActorTransform(); + } + + baseTransform = baseTransform.Inverse(); + FVector localPosition = baseTransform.TransformPosition(position); + float localX = 0, localY = 0; + if (bone->getParent()) { + bone->getParent()->worldToLocal(localPosition.X, localPosition.Z, localX, localY); + } else { + bone->worldToLocal(localPosition.X, localPosition.Z, localX, localY); + } + bone->setX(localX); + bone->setY(localY); + } +} + +void USpineSkeletonComponent::UpdateWorldTransform() { + CheckState(); + if (skeleton) { + skeleton->updateWorldTransform(Physics_Update); + } +} + +void USpineSkeletonComponent::SetToSetupPose() { + CheckState(); + if (skeleton) skeleton->setToSetupPose(); +} + +void USpineSkeletonComponent::SetBonesToSetupPose() { + CheckState(); + if (skeleton) skeleton->setBonesToSetupPose(); +} + +void USpineSkeletonComponent::SetSlotsToSetupPose() { + CheckState(); + if (skeleton) skeleton->setSlotsToSetupPose(); +} + +void USpineSkeletonComponent::SetScaleX(float scaleX) { + CheckState(); + if (skeleton) skeleton->setScaleX(scaleX); +} + +float USpineSkeletonComponent::GetScaleX() { + CheckState(); + if (skeleton) return skeleton->getScaleX(); + return 1; +} + +void USpineSkeletonComponent::SetScaleY(float scaleY) { + CheckState(); + if (skeleton) skeleton->setScaleY(scaleY); +} + +float USpineSkeletonComponent::GetScaleY() { + CheckState(); + if (skeleton) return skeleton->getScaleY(); + return 1; +} + +void USpineSkeletonComponent::GetBones(TArray &Bones) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getBones().size(); i < n; i++) { + Bones.Add(skeleton->getBones()[i]->getData().getName().buffer()); + } + } +} + +bool USpineSkeletonComponent::HasBone(const FString BoneName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findBone(TCHAR_TO_UTF8(*BoneName)) != nullptr; + } + return false; +} + +void USpineSkeletonComponent::GetSlots(TArray &Slots) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getSlots().size(); i < n; i++) { + Slots.Add(skeleton->getSlots()[i]->getData().getName().buffer()); + } + } +} + +bool USpineSkeletonComponent::HasSlot(const FString SlotName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findSlot(TCHAR_TO_UTF8(*SlotName)) != nullptr; + } + return false; +} + +void USpineSkeletonComponent::SetSlotColor(const FString SlotName, const FColor color) { + CheckState(); + if (skeleton) { + Slot *slot = skeleton->findSlot(TCHAR_TO_UTF8(*SlotName)); + if (slot) { + slot->getColor().set(color.R / 255.f, color.G / 255.f, color.B / 255.f, color.A / 255.f); + } + } +} + +void USpineSkeletonComponent::GetAnimations(TArray &Animations) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getData()->getAnimations().size(); i < n; i++) { + Animations.Add(skeleton->getData()->getAnimations()[i]->getName().buffer()); + } + } +} + +bool USpineSkeletonComponent::HasAnimation(FString AnimationName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*AnimationName)) != nullptr; + } + return false; +} + +float USpineSkeletonComponent::GetAnimationDuration(FString AnimationName) { + CheckState(); + if (skeleton) { + Animation *animation = skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*AnimationName)); + if (animation == nullptr) return 0; + else + return animation->getDuration(); + } + return 0; +} + +void USpineSkeletonComponent::PhysicsTranslate(float x, float y) { + CheckState(); + if (skeleton) { + skeleton->physicsTranslate(x, y); + } +} + +void USpineSkeletonComponent::PhysicsRotate(float x, float y, float degrees) { + CheckState(); + if (skeleton) { + skeleton->physicsRotate(x, y, degrees); + } +} + +void USpineSkeletonComponent::ResetPhysicsConstraints() { + CheckState(); + if (skeleton) { + Vector &constraints = skeleton->getPhysicsConstraints(); + for (int i = 0, n = (int) constraints.size(); i < n; i++) { + constraints[i]->reset(); + } + } +} + +void USpineSkeletonComponent::BeginPlay() { + Super::BeginPlay(); +} + +void USpineSkeletonComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + InternalTick(DeltaTime); +} + +void USpineSkeletonComponent::InternalTick(float DeltaTime, bool CallDelegates, bool Preview) { + CheckState(); + + if (skeleton) { + if (CallDelegates) BeforeUpdateWorldTransform.Broadcast(this); + skeleton->updateWorldTransform(Physics_Update); + if (CallDelegates) AfterUpdateWorldTransform.Broadcast(this); + } +} + +void USpineSkeletonComponent::CheckState() { + bool needsUpdate = lastAtlas != Atlas || lastData != SkeletonData; + + if (!needsUpdate) { + // Are we doing a re-import? Then check if the underlying spine-cpp data + // has changed. + if (lastAtlas && lastAtlas == Atlas && lastData && lastData == SkeletonData) { + spine::Atlas *atlas = Atlas->GetAtlas(); + if (lastSpineAtlas != atlas) { + needsUpdate = true; + } + if (skeleton && skeleton->getData() != SkeletonData->GetSkeletonData(atlas)) { + needsUpdate = true; + } + } + } + + if (needsUpdate) { + DisposeState(); + + if (Atlas && SkeletonData) { + spine::SkeletonData *data = SkeletonData->GetSkeletonData(Atlas->GetAtlas()); + skeleton = new (__FILE__, __LINE__) Skeleton(data); + } + + lastAtlas = Atlas; + lastSpineAtlas = Atlas ? Atlas->GetAtlas() : nullptr; + lastData = SkeletonData; + } +} + +void USpineSkeletonComponent::DisposeState() { + if (skeleton) { + delete skeleton; + skeleton = nullptr; + } +} + +void USpineSkeletonComponent::FinishDestroy() { + DisposeState(); + Super::FinishDestroy(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonDataAsset.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonDataAsset.cpp new file mode 100644 index 0000000..002fa04 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonDataAsset.cpp @@ -0,0 +1,404 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineSkeletonDataAsset.h" +#include "EditorFramework/AssetImportData.h" +#include "Runtime/Core/Public/Misc/MessageDialog.h" +#include "SpinePlugin.h" +#include "spine/Version.h" +#include "spine/spine.h" +#include + +#define LOCTEXT_NAMESPACE "Spine" + +using namespace spine; + +FName USpineSkeletonDataAsset::GetSkeletonDataFileName() const { +#if WITH_EDITORONLY_DATA + TArray files; + if (importData) + importData->ExtractFilenames(files); + if (files.Num() > 0) + return FName(*files[0]); + else + return skeletonDataFileName; +#else + return skeletonDataFileName; +#endif +} + +#if WITH_EDITORONLY_DATA + +void USpineSkeletonDataAsset::SetSkeletonDataFileName( + const FName &SkeletonDataFileName) { + importData->UpdateFilenameOnly(SkeletonDataFileName.ToString()); + TArray files; + importData->ExtractFilenames(files); + if (files.Num() > 0) + this->skeletonDataFileName = FName(*files[0]); +} + +void USpineSkeletonDataAsset::PostInitProperties() { + if (!HasAnyFlags(RF_ClassDefaultObject)) + importData = NewObject(this, TEXT("AssetImportData")); + Super::PostInitProperties(); +} + +#if ((ENGINE_MAJOR_VERSION >= 5) && (ENGINE_MINOR_VERSION >= 4)) +void USpineSkeletonDataAsset::GetAssetRegistryTags(FAssetRegistryTagsContext Context) const { + if (importData) { + Context.AddTag(FAssetRegistryTag(SourceFileTagName(), importData->GetSourceData().ToJson(), FAssetRegistryTag::TT_Hidden)); + } + Super::GetAssetRegistryTags(Context); +} +#else +void USpineSkeletonDataAsset::GetAssetRegistryTags( + TArray &OutTags) const { + if (importData) { + OutTags.Add(FAssetRegistryTag(SourceFileTagName(), + importData->GetSourceData().ToJson(), + FAssetRegistryTag::TT_Hidden)); + } + + Super::GetAssetRegistryTags(OutTags); +} +#endif + +void USpineSkeletonDataAsset::Serialize(FArchive &Ar) { + Super::Serialize(Ar); +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION <= 27 + if (Ar.IsLoading() && Ar.UE4Ver() < VER_UE4_ASSET_IMPORT_DATA_AS_JSON && !importData) +#else + if (Ar.IsLoading() && Ar.UEVer() < VER_UE4_ASSET_IMPORT_DATA_AS_JSON && !importData) +#endif + importData = NewObject(this, TEXT("AssetImportData")); + LoadInfo(); +} + +#endif + +void USpineSkeletonDataAsset::ClearNativeData() { + for (auto &pair : atlasToNativeData) { + if (pair.Value.skeletonData) + delete pair.Value.skeletonData; + if (pair.Value.animationStateData) + delete pair.Value.animationStateData; + } + atlasToNativeData.Empty(); +} + +void USpineSkeletonDataAsset::BeginDestroy() { + ClearNativeData(); + + Super::BeginDestroy(); +} + +class SP_API NullAttachmentLoader : public AttachmentLoader { +public: + virtual RegionAttachment *newRegionAttachment(Skin &skin, const String &name, + const String &path, + Sequence *sequence) { + return new (__FILE__, __LINE__) RegionAttachment(name); + } + + virtual MeshAttachment *newMeshAttachment(Skin &skin, const String &name, + const String &path, + Sequence *sequence) { + return new (__FILE__, __LINE__) MeshAttachment(name); + } + + virtual BoundingBoxAttachment *newBoundingBoxAttachment(Skin &skin, + const String &name) { + return new (__FILE__, __LINE__) BoundingBoxAttachment(name); + } + + virtual PathAttachment *newPathAttachment(Skin &skin, const String &name) { + return new (__FILE__, __LINE__) PathAttachment(name); + } + + virtual PointAttachment *newPointAttachment(Skin &skin, const String &name) { + return new (__FILE__, __LINE__) PointAttachment(name); + } + + virtual ClippingAttachment *newClippingAttachment(Skin &skin, + const String &name) { + return new (__FILE__, __LINE__) ClippingAttachment(name); + } + + virtual void configureAttachment(Attachment *attachment) {} +}; + +void USpineSkeletonDataAsset::SetRawData(TArray &Data) { + this->rawData.Empty(); + this->rawData.Append(Data); + + ClearNativeData(); + + LoadInfo(); +} + +static bool checkVersion(const char *version) { + if (!version) + return false; + char *result = (char *) (strstr(version, SPINE_VERSION_STRING) - version); + return result == 0; +} + +static bool checkJson(const char *jsonData) { + Json json(jsonData); + Json *skeleton = Json::getItem(&json, "skeleton"); + if (!skeleton) + return false; + const char *version = Json::getString(skeleton, "spine", 0); + if (!version) + return false; + + return checkVersion(version); +} + +struct BinaryInput { + const unsigned char *cursor; + const unsigned char *end; +}; + +static unsigned char readByte(BinaryInput *input) { return *input->cursor++; } + +static int readVarint(BinaryInput *input, bool optimizePositive) { + unsigned char b = readByte(input); + int value = b & 0x7F; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 7; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 14; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 21; + if (b & 0x80) + value |= (readByte(input) & 0x7F) << 28; + } + } + } + + if (!optimizePositive) { + value = (((unsigned int) value >> 1) ^ -(value & 1)); + } + + return value; +} + +static char *readString(BinaryInput *input) { + int length = readVarint(input, true); + char *string; + if (length == 0) { + return NULL; + } + string = SpineExtension::alloc(length, __FILE__, __LINE__); + memcpy(string, input->cursor, length - 1); + input->cursor += length - 1; + string[length - 1] = '\0'; + return string; +} + +static bool checkBinary(const char *binaryData, int length) { + BinaryInput input; + input.cursor = (const unsigned char *) binaryData; + input.end = (const unsigned char *) binaryData + length; + // Skip hash + input.cursor += 8; + char *version = readString(&input); + bool result = checkVersion(version); + SpineExtension::free(version, __FILE__, __LINE__); + return result; +} + +void USpineSkeletonDataAsset::LoadInfo() { +#if WITH_EDITORONLY_DATA + int dataLen = rawData.Num(); + if (dataLen == 0) + return; + NullAttachmentLoader loader; + SkeletonData *skeletonData = nullptr; + if (skeletonDataFileName.GetPlainNameString().Contains(TEXT(".json"))) { + SkeletonJson *json = new (__FILE__, __LINE__) SkeletonJson(&loader); + if (checkJson((const char *) rawData.GetData())) + skeletonData = json->readSkeletonData((const char *) rawData.GetData()); + if (!skeletonData) { + FMessageDialog::Debugf(FText::FromString( + FString("Couldn't load skeleton data and/or atlas. Please ensure the " + "version of your exported data matches your runtime " + "version.\n\n") + + skeletonDataFileName.GetPlainNameString() + FString("\n\n") + + UTF8_TO_TCHAR(json->getError().buffer()))); + UE_LOG(SpineLog, Error, TEXT("Couldn't load skeleton data and atlas: %s"), + UTF8_TO_TCHAR(json->getError().buffer())); + } + delete json; + } else { + SkeletonBinary *binary = new (__FILE__, __LINE__) SkeletonBinary(&loader); + if (checkBinary((const char *) rawData.GetData(), (int) rawData.Num())) + skeletonData = binary->readSkeletonData( + (const unsigned char *) rawData.GetData(), (int) rawData.Num()); + if (!skeletonData) { + FMessageDialog::Debugf(FText::FromString( + FString("Couldn't load skeleton data and/or atlas. Please ensure the " + "version of your exported data matches your runtime " + "version.\n\n") + + skeletonDataFileName.GetPlainNameString() + FString("\n\n") + + UTF8_TO_TCHAR(binary->getError().buffer()))); + UE_LOG(SpineLog, Error, TEXT("Couldn't load skeleton data and atlas: %s"), + UTF8_TO_TCHAR(binary->getError().buffer())); + } + delete binary; + } + if (skeletonData) { + Bones.Empty(); + for (int i = 0; i < skeletonData->getBones().size(); i++) + Bones.Add(UTF8_TO_TCHAR(skeletonData->getBones()[i]->getName().buffer())); + Skins.Empty(); + for (int i = 0; i < skeletonData->getSkins().size(); i++) + Skins.Add(UTF8_TO_TCHAR(skeletonData->getSkins()[i]->getName().buffer())); + Slots.Empty(); + for (int i = 0; i < skeletonData->getSlots().size(); i++) + Slots.Add(UTF8_TO_TCHAR(skeletonData->getSlots()[i]->getName().buffer())); + Animations.Empty(); + for (int i = 0; i < skeletonData->getAnimations().size(); i++) + Animations.Add( + UTF8_TO_TCHAR(skeletonData->getAnimations()[i]->getName().buffer())); + Events.Empty(); + for (int i = 0; i < skeletonData->getEvents().size(); i++) + Events.Add( + UTF8_TO_TCHAR(skeletonData->getEvents()[i]->getName().buffer())); + delete skeletonData; + } +#endif +} + +SkeletonData *USpineSkeletonDataAsset::GetSkeletonData(Atlas *Atlas) { + SkeletonData *skeletonData = nullptr; + AnimationStateData *animationStateData = nullptr; + if (atlasToNativeData.Contains(Atlas)) { + skeletonData = atlasToNativeData[Atlas].skeletonData; + animationStateData = atlasToNativeData[Atlas].animationStateData; + } + + if (!skeletonData) { + int dataLen = rawData.Num(); + if (skeletonDataFileName.GetPlainNameString().Contains(TEXT(".json"))) { + SkeletonJson *json = new (__FILE__, __LINE__) SkeletonJson(Atlas); + if (checkJson((const char *) rawData.GetData())) + skeletonData = json->readSkeletonData((const char *) rawData.GetData()); + if (!skeletonData) { +#if WITH_EDITORONLY_DATA + FMessageDialog::Debugf(FText::FromString( + FString("Couldn't load skeleton data and/or atlas. Please ensure " + "the version of your exported data matches your runtime " + "version.\n\n") + + skeletonDataFileName.GetPlainNameString() + FString("\n\n") + + UTF8_TO_TCHAR(json->getError().buffer()))); +#endif + UE_LOG(SpineLog, Error, + TEXT("Couldn't load skeleton data and atlas: %s"), + UTF8_TO_TCHAR(json->getError().buffer())); + } + delete json; + } else { + SkeletonBinary *binary = new (__FILE__, __LINE__) SkeletonBinary(Atlas); + if (checkBinary((const char *) rawData.GetData(), (int) rawData.Num())) + skeletonData = binary->readSkeletonData( + (const unsigned char *) rawData.GetData(), (int) rawData.Num()); + if (!skeletonData) { +#if WITH_EDITORONLY_DATA + FMessageDialog::Debugf(FText::FromString( + FString("Couldn't load skeleton data and/or atlas. Please ensure " + "the version of your exported data matches your runtime " + "version.\n\n") + + skeletonDataFileName.GetPlainNameString() + FString("\n\n") + + UTF8_TO_TCHAR(binary->getError().buffer()))); +#endif + UE_LOG(SpineLog, Error, + TEXT("Couldn't load skeleton data and atlas: %s"), + UTF8_TO_TCHAR(binary->getError().buffer())); + } + delete binary; + } + + if (skeletonData) { + animationStateData = + new (__FILE__, __LINE__) AnimationStateData(skeletonData); + SetMixes(animationStateData); + atlasToNativeData.Add(Atlas, {skeletonData, animationStateData}); + } + } + + return skeletonData; +} + +void USpineSkeletonDataAsset::SetMixes(AnimationStateData *animationStateData) { + for (auto &data : MixData) { + if (!data.From.IsEmpty() && !data.To.IsEmpty()) { + std::string fromChar = TCHAR_TO_UTF8(*data.From); + std::string toChar = TCHAR_TO_UTF8(*data.To); + animationStateData->setMix(fromChar.c_str(), toChar.c_str(), data.Mix); + } + } + animationStateData->setDefaultMix(DefaultMix); +} + +AnimationStateData * +USpineSkeletonDataAsset::GetAnimationStateData(Atlas *atlas) { + if (!atlasToNativeData.Contains(atlas)) + return nullptr; + AnimationStateData *data = atlasToNativeData[atlas].animationStateData; + SetMixes(data); + return data; +} + +void USpineSkeletonDataAsset::SetMix(const FString &from, const FString &to, + float mix) { + FSpineAnimationStateMixData data; + data.From = from; + data.To = to; + data.Mix = mix; + this->MixData.Add(data); + for (auto &pair : atlasToNativeData) { + SetMixes(pair.Value.animationStateData); + } +} + +float USpineSkeletonDataAsset::GetMix(const FString &from, const FString &to) { + for (auto &data : MixData) { + if (data.From.Equals(from) && data.To.Equals(to)) + return data.Mix; + } + return 0; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonRendererComponent.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonRendererComponent.cpp new file mode 100644 index 0000000..5f5c18f --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonRendererComponent.cpp @@ -0,0 +1,360 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineSkeletonRendererComponent.h" + +#include "SpineAtlasAsset.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "spine/spine.h" +#include "UObject/ConstructorHelpers.h" +#if ENGINE_MAJOR_VERSION >= 5 +#include "PhysicsEngine/BodySetup.h" +#endif + +#define LOCTEXT_NAMESPACE "Spine" + +using namespace spine; + +USpineSkeletonRendererComponent::USpineSkeletonRendererComponent(const FObjectInitializer &ObjectInitializer) + : UProceduralMeshComponent(ObjectInitializer) { + PrimaryComponentTick.bCanEverTick = true; + bTickInEditor = true; + bAutoActivate = true; + + static ConstructorHelpers::FObjectFinder NormalMaterialRef(TEXT("/SpinePlugin/SpineUnlitNormalMaterial")); + NormalBlendMaterial = NormalMaterialRef.Object; + + static ConstructorHelpers::FObjectFinder AdditiveMaterialRef(TEXT("/SpinePlugin/SpineUnlitAdditiveMaterial")); + AdditiveBlendMaterial = AdditiveMaterialRef.Object; + + static ConstructorHelpers::FObjectFinder MultiplyMaterialRef(TEXT("/SpinePlugin/SpineUnlitMultiplyMaterial")); + MultiplyBlendMaterial = MultiplyMaterialRef.Object; + + static ConstructorHelpers::FObjectFinder ScreenMaterialRef(TEXT("/SpinePlugin/SpineUnlitScreenMaterial")); + ScreenBlendMaterial = ScreenMaterialRef.Object; + + TextureParameterName = FName(TEXT("SpriteTexture")); + + worldVertices.ensureCapacity(1024 * 2); + + SetTickGroup(TG_EndPhysics); +} + +void USpineSkeletonRendererComponent::FinishDestroy() { + Super::FinishDestroy(); +} + +void USpineSkeletonRendererComponent::BeginPlay() { + Super::BeginPlay(); +} + +void USpineSkeletonRendererComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + AActor *owner = GetOwner(); + if (owner) { + UClass *skeletonClass = USpineSkeletonComponent::StaticClass(); + USpineSkeletonComponent *skeletonComponent = Cast(owner->GetComponentByClass(skeletonClass)); + + UpdateRenderer(skeletonComponent); + } +} + +void USpineSkeletonRendererComponent::UpdateRenderer(USpineSkeletonComponent *component) { + if (component && !component->IsBeingDestroyed() && component->GetSkeleton() && component->Atlas) { + component->GetSkeleton()->getColor().set(Color.R, Color.G, Color.B, Color.A); + + if (atlasNormalBlendMaterials.Num() != component->Atlas->atlasPages.Num()) { + atlasNormalBlendMaterials.SetNum(0); + atlasAdditiveBlendMaterials.SetNum(0); + atlasMultiplyBlendMaterials.SetNum(0); + atlasScreenBlendMaterials.SetNum(0); + + for (int i = 0; i < component->Atlas->atlasPages.Num(); i++) { + AtlasPage *currPage = component->Atlas->GetAtlas()->getPages()[i]; + + UMaterialInstanceDynamic *material = UMaterialInstanceDynamic::Create(NormalBlendMaterial, this); + material->SetTextureParameterValue(TextureParameterName, component->Atlas->atlasPages[i]); + atlasNormalBlendMaterials.Add(material); + + material = UMaterialInstanceDynamic::Create(AdditiveBlendMaterial, this); + material->SetTextureParameterValue(TextureParameterName, component->Atlas->atlasPages[i]); + atlasAdditiveBlendMaterials.Add(material); + + material = UMaterialInstanceDynamic::Create(MultiplyBlendMaterial, this); + material->SetTextureParameterValue(TextureParameterName, component->Atlas->atlasPages[i]); + atlasMultiplyBlendMaterials.Add(material); + + material = UMaterialInstanceDynamic::Create(ScreenBlendMaterial, this); + material->SetTextureParameterValue(TextureParameterName, component->Atlas->atlasPages[i]); + atlasScreenBlendMaterials.Add(material); + } + } else { + for (int i = 0; i < component->Atlas->atlasPages.Num(); i++) { + UTexture2D *texture = component->Atlas->atlasPages[i]; + UpdateMaterial(texture, atlasNormalBlendMaterials[i], NormalBlendMaterial); + UpdateMaterial(texture, atlasAdditiveBlendMaterials[i], AdditiveBlendMaterial); + UpdateMaterial(texture, atlasMultiplyBlendMaterials[i], MultiplyBlendMaterial); + UpdateMaterial(texture, atlasScreenBlendMaterials[i], ScreenBlendMaterial); + } + } + UpdateMesh(component, component->GetSkeleton()); + } else { + ClearAllMeshSections(); + } +} + +void USpineSkeletonRendererComponent::UpdateMaterial(UTexture2D *Texture, UMaterialInstanceDynamic *&CurrentInstance, UMaterialInterface *ParentMaterial) { + + UTexture *oldTexture = nullptr; + if (!CurrentInstance || !CurrentInstance->GetTextureParameterValue(TextureParameterName, oldTexture) || + oldTexture != Texture || CurrentInstance->Parent != ParentMaterial) { + + UMaterialInstanceDynamic *material = UMaterialInstanceDynamic::Create(ParentMaterial, this); + material->SetTextureParameterValue(TextureParameterName, Texture); + CurrentInstance = material; + } +} + +void USpineSkeletonRendererComponent::Flush(int &Idx, TArray &Vertices, TArray &Indices, TArray &Normals, TArray &Uvs, TArray &Colors, UMaterialInstanceDynamic *Material) { + if (Vertices.Num() == 0) return; + SetMaterial(Idx, Material); + + bool bShouldCreateCollision = false; + if (bCreateCollision) { + UWorld *world = GetWorld(); + if (world && world->IsGameWorld()) { + bShouldCreateCollision = true; + } + } + + GetBodySetup()->bGenerateMirroredCollision = GetComponentScale().X < 0 || GetComponentScale().Y < 0 || GetComponentScale().Z < 0; + CreateMeshSection(Idx, Vertices, Indices, Normals, Uvs, Colors, TArray(), bShouldCreateCollision); + + Vertices.SetNum(0); + Indices.SetNum(0); + Normals.SetNum(0); + Uvs.SetNum(0); + Colors.SetNum(0); + Idx++; +} + +void USpineSkeletonRendererComponent::UpdateMesh(USpineSkeletonComponent *component, Skeleton *Skeleton) { + vertices.Empty(); + indices.Empty(); + normals.Empty(); + uvs.Empty(); + colors.Empty(); + + int idx = 0; + int meshSection = 0; + UMaterialInstanceDynamic *lastMaterial = nullptr; + + ClearAllMeshSections(); + + // Early out if skeleton is invisible + if (Skeleton->getColor().a == 0) return; + + float depthOffset = 0; + unsigned short quadIndices[] = {0, 1, 2, 0, 2, 3}; + + for (size_t i = 0; i < Skeleton->getSlots().size(); ++i) { + Vector *attachmentVertices = &worldVertices; + unsigned short *attachmentIndices = nullptr; + int numVertices; + int numIndices; + AtlasRegion *attachmentAtlasRegion = nullptr; + spine::Color attachmentColor; + attachmentColor.set(1, 1, 1, 1); + float *attachmentUvs = nullptr; + + Slot *slot = Skeleton->getDrawOrder()[i]; + Attachment *attachment = slot->getAttachment(); + + if (slot->getColor().a == 0 || !slot->getBone().isActive()) { + clipper.clipEnd(*slot); + continue; + } + + if (!attachment) { + clipper.clipEnd(*slot); + continue; + } + if (!attachment->getRTTI().isExactly(RegionAttachment::rtti) && !attachment->getRTTI().isExactly(MeshAttachment::rtti) && !attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { + clipper.clipEnd(*slot); + continue; + } + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = (RegionAttachment *) attachment; + + // Early out if region is invisible + if (regionAttachment->getColor().a == 0) { + clipper.clipEnd(*slot); + continue; + } + + attachmentColor.set(regionAttachment->getColor()); + attachmentVertices->setSize(8, 0); + regionAttachment->computeWorldVertices(*slot, *attachmentVertices, 0, 2); + attachmentAtlasRegion = (AtlasRegion *) regionAttachment->getRegion(); + attachmentIndices = quadIndices; + attachmentUvs = regionAttachment->getUVs().buffer(); + numVertices = 4; + numIndices = 6; + } else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + MeshAttachment *mesh = (MeshAttachment *) attachment; + + // Early out if region is invisible + if (mesh->getColor().a == 0) { + clipper.clipEnd(*slot); + continue; + } + + attachmentColor.set(mesh->getColor()); + attachmentVertices->setSize(mesh->getWorldVerticesLength(), 0); + mesh->computeWorldVertices(*slot, 0, mesh->getWorldVerticesLength(), attachmentVertices->buffer(), 0, 2); + attachmentAtlasRegion = (AtlasRegion *) mesh->getRegion(); + attachmentIndices = mesh->getTriangles().buffer(); + attachmentUvs = mesh->getUVs().buffer(); + numVertices = mesh->getWorldVerticesLength() >> 1; + numIndices = mesh->getTriangles().size(); + } else /* clipping */ { + ClippingAttachment *clip = (ClippingAttachment *) attachment; + clipper.clipStart(*slot, clip); + continue; + } + + if (clipper.isClipping()) { + clipper.clipTriangles(attachmentVertices->buffer(), attachmentIndices, numIndices, attachmentUvs, 2); + attachmentVertices = &clipper.getClippedVertices(); + numVertices = clipper.getClippedVertices().size() >> 1; + attachmentIndices = clipper.getClippedTriangles().buffer(); + numIndices = clipper.getClippedTriangles().size(); + attachmentUvs = clipper.getClippedUVs().buffer(); + if (clipper.getClippedTriangles().size() == 0) { + clipper.clipEnd(*slot); + continue; + } + } + + // if the user switches the atlas data while not having switched + // to the correct skeleton data yet, we won't find any regions. + // ignore regions for which we can't find a material + UMaterialInstanceDynamic *material = nullptr; + int foundPageIndex = (int) (intptr_t) attachmentAtlasRegion->rendererObject; + if (foundPageIndex == -1) { + clipper.clipEnd(*slot); + continue; + } + switch (slot->getData().getBlendMode()) { + case BlendMode_Additive: + if (foundPageIndex >= atlasAdditiveBlendMaterials.Num()) { + clipper.clipEnd(*slot); + continue; + } + material = atlasAdditiveBlendMaterials[foundPageIndex]; + break; + case BlendMode_Multiply: + if (foundPageIndex >= atlasMultiplyBlendMaterials.Num()) { + clipper.clipEnd(*slot); + continue; + } + material = atlasMultiplyBlendMaterials[foundPageIndex]; + break; + case BlendMode_Screen: + if (foundPageIndex >= atlasScreenBlendMaterials.Num()) { + clipper.clipEnd(*slot); + continue; + } + material = atlasScreenBlendMaterials[foundPageIndex]; + break; + case BlendMode_Normal: + default: + if (foundPageIndex >= atlasNormalBlendMaterials.Num()) { + clipper.clipEnd(*slot); + continue; + } + material = atlasNormalBlendMaterials[foundPageIndex]; + break; + } + + if (lastMaterial != material) { + Flush(meshSection, vertices, indices, normals, uvs, colors, lastMaterial); + lastMaterial = material; + idx = 0; + } + + SetMaterial(meshSection, material); + + uint8 r = static_cast(Skeleton->getColor().r * slot->getColor().r * attachmentColor.r * 255); + uint8 g = static_cast(Skeleton->getColor().g * slot->getColor().g * attachmentColor.g * 255); + uint8 b = static_cast(Skeleton->getColor().b * slot->getColor().b * attachmentColor.b * 255); + uint8 a = static_cast(Skeleton->getColor().a * slot->getColor().a * attachmentColor.a * 255); + + float *verticesPtr = attachmentVertices->buffer(); + for (int j = 0; j < numVertices << 1; j += 2) { + colors.Add(FColor(r, g, b, a)); + vertices.Add(FVector(verticesPtr[j], depthOffset, verticesPtr[j + 1])); + uvs.Add(FVector2D(attachmentUvs[j], attachmentUvs[j + 1])); + } + + for (int j = 0; j < numIndices; j++) { + indices.Add(idx + attachmentIndices[j]); + } + + int numTriangles = indices.Num() / 3; + for (int j = 0; j < numTriangles; j++) { + const int triangleIndex = j * 3; + if (FVector::CrossProduct( + vertices[indices[triangleIndex + 2]] - vertices[indices[triangleIndex]], + vertices[indices[triangleIndex + 1]] - vertices[indices[triangleIndex]]) + .Y < 0.f) { + const int32 targetVertex = indices[triangleIndex]; + indices[triangleIndex] = indices[triangleIndex + 2]; + indices[triangleIndex + 2] = targetVertex; + } + } + + FVector normal = FVector(0, 1, 0); + for (int j = 0; j < numVertices; j++) { + normals.Add(normal); + } + + idx += numVertices; + depthOffset += this->DepthOffset; + + clipper.clipEnd(*slot); + } + + Flush(meshSection, vertices, indices, normals, uvs, colors, lastMaterial); + clipper.clipEnd(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp new file mode 100644 index 0000000..e7d8118 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineWidget.cpp @@ -0,0 +1,564 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include "SpineWidget.h" +#include "SSpineWidget.h" +#include "SpineSkeletonAnimationComponent.h" +#include "spine/spine.h" + +#define LOCTEXT_NAMESPACE "Spine" + +using namespace spine; + +void callbackWidget(AnimationState *state, spine::EventType type, TrackEntry *entry, Event *event) { + USpineWidget *component = (USpineWidget *) state->getRendererObject(); + + if (entry->getRendererObject()) { + UTrackEntry *uEntry = (UTrackEntry *) entry->getRendererObject(); + if (type == EventType_Start) { + component->AnimationStart.Broadcast(uEntry); + uEntry->AnimationStart.Broadcast(uEntry); + } else if (type == EventType_Interrupt) { + component->AnimationInterrupt.Broadcast(uEntry); + uEntry->AnimationInterrupt.Broadcast(uEntry); + } else if (type == EventType_Event) { + FSpineEvent evt; + evt.SetEvent(event); + component->AnimationEvent.Broadcast(uEntry, evt); + uEntry->AnimationEvent.Broadcast(uEntry, evt); + } else if (type == EventType_Complete) { + component->AnimationComplete.Broadcast(uEntry); + uEntry->AnimationComplete.Broadcast(uEntry); + } else if (type == EventType_End) { + component->AnimationEnd.Broadcast(uEntry); + uEntry->AnimationEnd.Broadcast(uEntry); + } else if (type == EventType_Dispose) { + component->AnimationDispose.Broadcast(uEntry); + uEntry->AnimationDispose.Broadcast(uEntry); + uEntry->SetTrackEntry(nullptr); + component->GCTrackEntry(uEntry); + } + } +} + +USpineWidget::USpineWidget(const FObjectInitializer &ObjectInitializer) : Super(ObjectInitializer) { + static ConstructorHelpers::FObjectFinder NormalMaterialRef(TEXT("/SpinePlugin/UI_SpineUnlitNormalMaterial")); + NormalBlendMaterial = NormalMaterialRef.Object; + + static ConstructorHelpers::FObjectFinder AdditiveMaterialRef(TEXT("/SpinePlugin/UI_SpineUnlitAdditiveMaterial")); + AdditiveBlendMaterial = AdditiveMaterialRef.Object; + + static ConstructorHelpers::FObjectFinder MultiplyMaterialRef(TEXT("/SpinePlugin/UI_SpineUnlitMultiplyMaterial")); + MultiplyBlendMaterial = MultiplyMaterialRef.Object; + + static ConstructorHelpers::FObjectFinder ScreenMaterialRef(TEXT("/SpinePlugin/UI_SpineUnlitScreenMaterial")); + ScreenBlendMaterial = ScreenMaterialRef.Object; + + TextureParameterName = FName(TEXT("SpriteTexture")); + + physicsTimeScale = 1.0f; + worldVertices.ensureCapacity(1024 * 2); + + bAutoPlaying = true; +} + +void USpineWidget::SynchronizeProperties() { + Super::SynchronizeProperties(); + + if (slateWidget.IsValid()) { + CheckState(); + if (skeleton) { + if (!bSkinInitialized) {// blueprint On Initialized may be called beforehand + if (InitialSkin != "") SetSkin(InitialSkin); +#if WITH_EDITOR + if (IsDesignTime()) { + if (InitialSkin == "") SetSkin("default"); + bSkinInitialized = false;// allow multiple edits in editor + } +#endif + } + Tick(0, false); + slateWidget->SetData(this); + } else { + slateWidget->SetData(nullptr); + } + for (UTrackEntry *entry : trackEntries) { + if (entry && entry->GetTrackEntry()) { + entry->GetTrackEntry()->setRendererObject(nullptr); + } + } + trackEntries.Empty(); + } +} + +void USpineWidget::ReleaseSlateResources(bool bReleaseChildren) { + Super::ReleaseSlateResources(bReleaseChildren); + slateWidget.Reset(); +} + +TSharedRef USpineWidget::RebuildWidget() { + this->slateWidget = SNew(SSpineWidget); + return this->slateWidget.ToSharedRef(); +} + +#if WITH_EDITOR +const FText USpineWidget::GetPaletteCategory() { + return LOCTEXT("Spine", "Spine"); +} +#endif + +void USpineWidget::Tick(float DeltaTime, bool CallDelegates) { + CheckState(); + + if (state && bAutoPlaying) { + state->update(DeltaTime); + state->apply(*skeleton); + if (CallDelegates) BeforeUpdateWorldTransform.Broadcast(this); + skeleton->update(physicsTimeScale * DeltaTime); + skeleton->updateWorldTransform(Physics_Update); + if (CallDelegates) AfterUpdateWorldTransform.Broadcast(this); + } +} + +void USpineWidget::CheckState() { + bool needsUpdate = lastAtlas != Atlas || lastData != SkeletonData; + + if (!needsUpdate) { + // Are we doing a re-import? Then check if the underlying spine-cpp data + // has changed. + if (lastAtlas && lastAtlas == Atlas && lastData && lastData == SkeletonData) { + spine::Atlas *atlas = Atlas->GetAtlas(); + if (lastSpineAtlas != atlas) { + needsUpdate = true; + } + if (skeleton && skeleton->getData() != SkeletonData->GetSkeletonData(atlas)) { + needsUpdate = true; + } + } + } + + if (needsUpdate) { + DisposeState(); + + if (Atlas && SkeletonData) { + spine::SkeletonData *data = SkeletonData->GetSkeletonData(Atlas->GetAtlas()); + if (data) { + skeleton = new (__FILE__, __LINE__) Skeleton(data); + AnimationStateData *stateData = SkeletonData->GetAnimationStateData(Atlas->GetAtlas()); + state = new (__FILE__, __LINE__) AnimationState(stateData); + state->setRendererObject((void *) this); + state->setListener(callbackWidget); + trackEntries.Empty(); + skeleton->setToSetupPose(); + skeleton->updateWorldTransform(Physics_Update); + slateWidget->SetData(this); + } + } + + lastAtlas = Atlas; + lastSpineAtlas = Atlas ? Atlas->GetAtlas() : nullptr; + lastData = SkeletonData; + } +} + +void USpineWidget::DisposeState() { + if (state) { + delete state; + state = nullptr; + } + + if (skeleton) { + delete skeleton; + skeleton = nullptr; + } + + if (customSkin) { + delete customSkin; + customSkin = nullptr; + } + + trackEntries.Empty(); +} + +void USpineWidget::FinishDestroy() { + DisposeState(); + Super::FinishDestroy(); +} + +bool USpineWidget::SetSkin(const FString skinName) { + CheckState(); + if (skeleton) { + spine::Skin *skin = skeleton->getData()->findSkin(TCHAR_TO_UTF8(*skinName)); + if (!skin) return false; + skeleton->setSkin(skin); + bSkinInitialized = true; + return true; + } else + return false; +} + +bool USpineWidget::SetSkins(UPARAM(ref) TArray &SkinNames) { + CheckState(); + if (skeleton) { + spine::Skin *newSkin = new spine::Skin("__spine-ue3_custom_skin"); + for (auto &skinName : SkinNames) { + spine::Skin *skin = skeleton->getData()->findSkin(TCHAR_TO_UTF8(*skinName)); + if (!skin) { + delete newSkin; + return false; + } + newSkin->addSkin(skin); + } + skeleton->setSkin(newSkin); + bSkinInitialized = true; + if (customSkin != nullptr) { + delete customSkin; + } + customSkin = newSkin; + return true; + } else + return false; +} + +void USpineWidget::GetSkins(TArray &Skins) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getData()->getSkins().size(); i < n; i++) { + Skins.Add(skeleton->getData()->getSkins()[i]->getName().buffer()); + } + } +} + +bool USpineWidget::HasSkin(const FString skinName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findSkin(TCHAR_TO_UTF8(*skinName)) != nullptr; + } + return false; +} + +bool USpineWidget::SetAttachment(const FString slotName, const FString attachmentName) { + CheckState(); + if (skeleton) { + if (attachmentName.IsEmpty()) { + skeleton->setAttachment(TCHAR_TO_UTF8(*slotName), NULL); + return true; + } + if (!skeleton->getAttachment(TCHAR_TO_UTF8(*slotName), TCHAR_TO_UTF8(*attachmentName))) return false; + skeleton->setAttachment(TCHAR_TO_UTF8(*slotName), TCHAR_TO_UTF8(*attachmentName)); + return true; + } + return false; +} + +void USpineWidget::UpdateWorldTransform() { + CheckState(); + if (skeleton) { + skeleton->updateWorldTransform(Physics_Update); + } +} + +void USpineWidget::SetToSetupPose() { + CheckState(); + if (skeleton) skeleton->setToSetupPose(); +} + +void USpineWidget::SetBonesToSetupPose() { + CheckState(); + if (skeleton) skeleton->setBonesToSetupPose(); +} + +void USpineWidget::SetSlotsToSetupPose() { + CheckState(); + if (skeleton) skeleton->setSlotsToSetupPose(); +} + +void USpineWidget::SetScaleX(float scaleX) { + CheckState(); + if (skeleton) skeleton->setScaleX(scaleX); +} + +float USpineWidget::GetScaleX() { + CheckState(); + if (skeleton) return skeleton->getScaleX(); + return 1; +} + +void USpineWidget::SetScaleY(float scaleY) { + CheckState(); + if (skeleton) skeleton->setScaleY(scaleY); +} + +float USpineWidget::GetScaleY() { + CheckState(); + if (skeleton) return skeleton->getScaleY(); + return 1; +} + +void USpineWidget::GetBones(TArray &Bones) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getBones().size(); i < n; i++) { + Bones.Add(skeleton->getBones()[i]->getData().getName().buffer()); + } + } +} + +bool USpineWidget::HasBone(const FString BoneName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findBone(TCHAR_TO_UTF8(*BoneName)) != nullptr; + } + return false; +} + +FTransform USpineWidget::GetBoneTransform(const FString &BoneName) { + CheckState(); + if (skeleton) { + Bone *bone = skeleton->findBone(TCHAR_TO_UTF8(*BoneName)); + if (!bone) return FTransform(); + + FMatrix localTransform; + localTransform.SetIdentity(); + localTransform.SetAxis(2, FVector(bone->getA(), 0, bone->getC())); + localTransform.SetAxis(0, FVector(bone->getB(), 0, bone->getD())); + localTransform.SetOrigin(FVector(bone->getWorldX(), 0, bone->getWorldY())); + + FTransform result; + result.SetFromMatrix(localTransform); + return result; + } + return FTransform(); +} + +void USpineWidget::GetSlots(TArray &Slots) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getSlots().size(); i < n; i++) { + Slots.Add(skeleton->getSlots()[i]->getData().getName().buffer()); + } + } +} + +bool USpineWidget::HasSlot(const FString SlotName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findSlot(TCHAR_TO_UTF8(*SlotName)) != nullptr; + } + return false; +} + +void USpineWidget::SetSlotColor(const FString SlotName, const FColor SlotColor) { + CheckState(); + if (skeleton) { + spine::Slot *slot = skeleton->findSlot(TCHAR_TO_UTF8(*SlotName)); + if (slot) { + slot->getColor().set(SlotColor.R / 255.f, SlotColor.G / 255.f, SlotColor.B / 255.f, SlotColor.A / 255.f); + } + } +} + +void USpineWidget::GetAnimations(TArray &Animations) { + CheckState(); + if (skeleton) { + for (size_t i = 0, n = skeleton->getData()->getAnimations().size(); i < n; i++) { + Animations.Add(skeleton->getData()->getAnimations()[i]->getName().buffer()); + } + } +} + +bool USpineWidget::HasAnimation(FString AnimationName) { + CheckState(); + if (skeleton) { + return skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*AnimationName)) != nullptr; + } + return false; +} + +float USpineWidget::GetAnimationDuration(FString AnimationName) { + CheckState(); + if (skeleton) { + spine::Animation *animation = skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*AnimationName)); + if (animation == nullptr) return 0; + else + return animation->getDuration(); + } + return 0; +} + +void USpineWidget::SetAutoPlay(bool bInAutoPlays) { + bAutoPlaying = bInAutoPlays; +} + +void USpineWidget::SetPlaybackTime(float InPlaybackTime, bool bCallDelegates) { + CheckState(); + + if (state && state->getCurrent(0)) { + spine::Animation *CurrentAnimation = state->getCurrent(0)->getAnimation(); + const float CurrentTime = state->getCurrent(0)->getTrackTime(); + InPlaybackTime = FMath::Clamp(InPlaybackTime, 0.0f, CurrentAnimation->getDuration()); + const float DeltaTime = InPlaybackTime - CurrentTime; + state->update(DeltaTime); + state->apply(*skeleton); + + //Call delegates and perform the world transform + if (bCallDelegates) { + BeforeUpdateWorldTransform.Broadcast(this); + } + skeleton->updateWorldTransform(Physics_Update); + if (bCallDelegates) { + AfterUpdateWorldTransform.Broadcast(this); + } + } +} + +void USpineWidget::SetTimeScale(float timeScale) { + CheckState(); + if (state) state->setTimeScale(timeScale); +} + +float USpineWidget::GetTimeScale() { + CheckState(); + if (state) return state->getTimeScale(); + return 1; +} + +UTrackEntry *USpineWidget::SetAnimation(int trackIndex, FString animationName, bool loop) { + CheckState(); + if (state && skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*animationName))) { + state->disableQueue(); + TrackEntry *entry = state->setAnimation(trackIndex, TCHAR_TO_UTF8(*animationName), loop); + state->enableQueue(); + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } else + return NewObject(); +} + +UTrackEntry *USpineWidget::AddAnimation(int trackIndex, FString animationName, bool loop, float delay) { + CheckState(); + if (state && skeleton->getData()->findAnimation(TCHAR_TO_UTF8(*animationName))) { + state->disableQueue(); + TrackEntry *entry = state->addAnimation(trackIndex, TCHAR_TO_UTF8(*animationName), loop, delay); + state->enableQueue(); + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } else + return NewObject(); +} + +UTrackEntry *USpineWidget::SetEmptyAnimation(int trackIndex, float mixDuration) { + CheckState(); + if (state) { + TrackEntry *entry = state->setEmptyAnimation(trackIndex, mixDuration); + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } else + return NewObject(); +} + +UTrackEntry *USpineWidget::AddEmptyAnimation(int trackIndex, float mixDuration, float delay) { + CheckState(); + if (state) { + TrackEntry *entry = state->addEmptyAnimation(trackIndex, mixDuration, delay); + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } else + return NewObject(); +} + +UTrackEntry *USpineWidget::GetCurrent(int trackIndex) { + CheckState(); + if (state && state->getCurrent(trackIndex)) { + TrackEntry *entry = state->getCurrent(trackIndex); + if (entry->getRendererObject()) { + return (UTrackEntry *) entry->getRendererObject(); + } else { + UTrackEntry *uEntry = NewObject(); + uEntry->SetTrackEntry(entry); + trackEntries.Add(uEntry); + return uEntry; + } + } else + return NewObject(); +} + +void USpineWidget::ClearTracks() { + CheckState(); + if (state) { + state->clearTracks(); + } +} + +void USpineWidget::ClearTrack(int trackIndex) { + CheckState(); + if (state) { + state->clearTrack(trackIndex); + } +} + +void USpineWidget::PhysicsTranslate(float x, float y) { + CheckState(); + if (skeleton) { + skeleton->physicsTranslate(x, y); + } +} + +void USpineWidget::PhysicsRotate(float x, float y, float degrees) { + CheckState(); + if (skeleton) { + skeleton->physicsRotate(x, y, degrees); + } +} + +void USpineWidget::ResetPhysicsConstraints() { + CheckState(); + if (skeleton) { + Vector &constraints = skeleton->getPhysicsConstraints(); + for (int i = 0, n = (int) constraints.size(); i < n; i++) { + constraints[i]->reset(); + } + } +} + +void USpineWidget::SetPhysicsTimeScale(float scale) { + physicsTimeScale = scale; +} + +float USpineWidget::GetPhysicsTimeScale() { + return physicsTimeScale; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SSpineWidget.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SSpineWidget.h new file mode 100644 index 0000000..bfa6eae --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SSpineWidget.h @@ -0,0 +1,64 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include "Slate/SMeshWidget.h" +#include "SlateCore.h" +#include "SpineAtlasAsset.h" +#include + +class USpineWidget; + +class SSpineWidget : public SMeshWidget { + +public: + SLATE_BEGIN_ARGS(SSpineWidget) : _MeshData(nullptr) {} + SLATE_ARGUMENT(USlateVectorArtData *, MeshData) + SLATE_END_ARGS() + + void Construct(const FArguments &Args); + + void SetData(USpineWidget *Widget); + FSlateBrush *brush; + +protected: + virtual int32 OnPaint(const FPaintArgs &Args, const FGeometry &AllottedGeometry, const FSlateRect &MyCullingRect, FSlateWindowElementList &OutDrawElements, int32 LayerId, const FWidgetStyle &InWidgetStyle, bool bParentEnabled) const override; + + void UpdateMesh(int32 LayerId, FSlateWindowElementList &OutDrawElements, const FGeometry &AllottedGeometry, spine::Skeleton *Skeleton); + + void Flush(int32 LayerId, FSlateWindowElementList &OutDrawElements, const FGeometry &AllottedGeometry, int &Idx, TArray &Vertices, TArray &Indices, TArray &Uvs, TArray &Colors, TArray &Colors2, UMaterialInstanceDynamic *Material); + + virtual FVector2D ComputeDesiredSize(float) const override; + + USpineWidget *widget; + FRenderData renderData; + FVector boundsMin; + FVector boundsSize; +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineAtlasAsset.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineAtlasAsset.h new file mode 100644 index 0000000..17789ff --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineAtlasAsset.h @@ -0,0 +1,75 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +// clang-format off +#include "Engine/Texture2D.h" +#include "spine/spine.h" +#include "SpineAtlasAsset.generated.h" +// clang-format on + +UCLASS(BlueprintType, ClassGroup = (Spine)) +class SPINEPLUGIN_API USpineAtlasAsset : public UPrimaryDataAsset { + GENERATED_BODY() + +public: + spine::Atlas *GetAtlas(); + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray atlasPages; + + void SetRawData(const FString &RawData); + + FName GetAtlasFileName() const; + + virtual void BeginDestroy() override; + +protected: + spine::Atlas *atlas = nullptr; + + UPROPERTY() + FString rawData; + + UPROPERTY() + FName atlasFileName; + +#if WITH_EDITORONLY_DATA + +public: + void SetAtlasFileName(const FName &AtlasFileName); + +protected: + UPROPERTY(VisibleAnywhere, Instanced, Category = ImportSettings) + class UAssetImportData *importData; + + virtual void PostInitProperties() override; + virtual void Serialize(FArchive &Ar) override; +#endif +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineBoneDriverComponent.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineBoneDriverComponent.h new file mode 100644 index 0000000..9c21fb0 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineBoneDriverComponent.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include "Components/SceneComponent.h" +#include "SpineBoneDriverComponent.generated.h" + +class USpineSkeletonComponent; + +UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) +class SPINEPLUGIN_API USpineBoneDriverComponent : public USceneComponent { + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite) + AActor *Target = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString BoneName; + + //Uses just this component when set to true. Updates owning actor otherwise. + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool UseComponentTransform = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool UsePosition = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool UseRotation = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool UseScale = true; + + USpineBoneDriverComponent(); + + virtual void BeginPlay() override; + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + +protected: + UFUNCTION() + void BeforeUpdateWorldTransform(USpineSkeletonComponent *skeleton); + + USpineSkeletonComponent *lastBoundComponent = nullptr; +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineBoneFollowerComponent.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineBoneFollowerComponent.h new file mode 100644 index 0000000..181eafd --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineBoneFollowerComponent.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include "Components/ActorComponent.h" +#include "Components/SceneComponent.h" +#include "SpineBoneFollowerComponent.generated.h" + + +UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) +class SPINEPLUGIN_API USpineBoneFollowerComponent : public USceneComponent { + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite) + AActor *Target = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString BoneName; + + //Updates just this component when set to true. Updates owning actor otherwise. + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool UseComponentTransform = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool UsePosition = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool UseRotation = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool UseScale = true; + + USpineBoneFollowerComponent(); + + virtual void BeginPlay() override; + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpinePlugin.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpinePlugin.h new file mode 100644 index 0000000..ffbb736 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpinePlugin.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include "Modules/ModuleManager.h" + +DECLARE_LOG_CATEGORY_EXTERN(SpineLog, Log, All); + +class SPINEPLUGIN_API SpinePlugin : public IModuleInterface { + +public: + static inline SpinePlugin &Get() { + return FModuleManager::LoadModuleChecked("SpinePlugin"); + } + + static inline bool IsAvailable() { + return FModuleManager::Get().IsModuleLoaded("SpinePlugin"); + } +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonAnimationComponent.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonAnimationComponent.h new file mode 100644 index 0000000..9dede80 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonAnimationComponent.h @@ -0,0 +1,339 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +// clang-format off +#include "Components/ActorComponent.h" +#include "SpineSkeletonComponent.h" +#include "spine/spine.h" +#include "SpineSkeletonAnimationComponent.generated.h" +// clang-format on + +USTRUCT(BlueprintType, Category = "Spine") +struct SPINEPLUGIN_API FSpineEvent { + GENERATED_BODY(); + +public: + FSpineEvent() : IntValue(0), FloatValue(0.0f), Time(0.0f) {} + + void SetEvent(spine::Event *event) { + Name = FString(UTF8_TO_TCHAR(event->getData().getName().buffer())); + if (!event->getStringValue().isEmpty()) { + StringValue = FString(UTF8_TO_TCHAR(event->getStringValue().buffer())); + } + this->IntValue = event->getIntValue(); + this->FloatValue = event->getFloatValue(); + this->Time = event->getTime(); + } + + UPROPERTY(BlueprintReadonly) + FString Name; + + UPROPERTY(BlueprintReadOnly) + FString StringValue; + + UPROPERTY(BlueprintReadOnly) + int IntValue; + + UPROPERTY(BlueprintReadOnly) + float FloatValue; + + UPROPERTY(BlueprintReadOnly) + float Time; +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineAnimationStartDelegate, UTrackEntry *, entry); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FSpineAnimationEventDelegate, UTrackEntry *, entry, FSpineEvent, evt); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineAnimationInterruptDelegate, UTrackEntry *, entry); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineAnimationCompleteDelegate, UTrackEntry *, entry); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineAnimationEndDelegate, UTrackEntry *, entry); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineAnimationDisposeDelegate, UTrackEntry *, entry); + +UCLASS(ClassGroup = (Spine), meta = (BlueprintSpawnableComponent), BlueprintType) +class SPINEPLUGIN_API UTrackEntry : public UObject { + GENERATED_BODY() + +public: + UTrackEntry() {} + + void SetTrackEntry(spine::TrackEntry *trackEntry); + spine::TrackEntry *GetTrackEntry() { return entry; } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + int GetTrackIndex() { return entry ? entry->getTrackIndex() : 0; } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + bool GetLoop() { return entry ? entry->getLoop() : false; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetLoop(bool loop) { + if (entry) entry->setLoop(loop); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetEventThreshold() { return entry ? entry->getEventThreshold() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetEventThreshold(float eventThreshold) { + if (entry) entry->setEventThreshold(eventThreshold); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetAlphaAttachmentThreshold() { return entry ? entry->getAlphaAttachmentThreshold() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetAlphaAttachmentThreshold(float attachmentThreshold) { + if (entry) entry->setAlphaAttachmentThreshold(attachmentThreshold); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetMixDrawOrderThreshold() { return entry ? entry->getMixDrawOrderThreshold() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetMixDrawOrderThreshold(float drawOrderThreshold) { + if (entry) entry->setMixDrawOrderThreshold(drawOrderThreshold); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetMixAttachmentThreshold() { return entry ? entry->getMixAttachmentThreshold() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetMixAttachmentThreshold(float drawOrderThreshold) { + if (entry) entry->setMixAttachmentThreshold(drawOrderThreshold); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetAnimationStart() { return entry ? entry->getAnimationStart() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetAnimationStart(float animationStart) { + if (entry) entry->setAnimationStart(animationStart); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetAnimationEnd() { return entry ? entry->getAnimationEnd() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetAnimationEnd(float animationEnd) { + if (entry) entry->setAnimationEnd(animationEnd); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetAnimationLast() { return entry ? entry->getAnimationLast() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetAnimationLast(float animationLast) { + if (entry) entry->setAnimationLast(animationLast); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetDelay() { return entry ? entry->getDelay() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetDelay(float delay) { + if (entry) entry->setDelay(delay); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetTrackTime() { return entry ? entry->getTrackTime() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetTrackTime(float trackTime) { + if (entry) entry->setTrackTime(trackTime); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetTrackEnd() { return entry ? entry->getTrackEnd() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetTrackEnd(float trackEnd) { + if (entry) entry->setTrackEnd(trackEnd); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetTimeScale() { return entry ? entry->getTimeScale() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetTimeScale(float timeScale) { + if (entry) entry->setTimeScale(timeScale); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetAlpha() { return entry ? entry->getAlpha() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetAlpha(float alpha) { + if (entry) entry->setAlpha(alpha); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetMixTime() { return entry ? entry->getMixTime() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetMixTime(float mixTime) { + if (entry) entry->setMixTime(mixTime); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float GetMixDuration() { return entry ? entry->getMixDuration() : 0; } + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + void SetMixDuration(float mixDuration) { + if (entry) entry->setMixDuration(mixDuration); + } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + FString getAnimationName() { return entry ? entry->getAnimation()->getName().buffer() : ""; } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + float getAnimationDuration() { return entry ? entry->getAnimation()->getDuration() : 0; } + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|TrackEntry") + bool isValidAnimation() { return entry != nullptr; } + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|TrackEntry") + FSpineAnimationStartDelegate AnimationStart; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|TrackEntry") + FSpineAnimationInterruptDelegate AnimationInterrupt; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|TrackEntry") + FSpineAnimationEventDelegate AnimationEvent; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|TrackEntry") + FSpineAnimationCompleteDelegate AnimationComplete; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|TrackEntry") + FSpineAnimationEndDelegate AnimationEnd; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|TrackEntry") + FSpineAnimationDisposeDelegate AnimationDispose; + + virtual void BeginDestroy() override; + +protected: + spine::TrackEntry *entry = nullptr; +}; + +class USpineAtlasAsset; +UCLASS(ClassGroup = (Spine), meta = (BlueprintSpawnableComponent)) +class SPINEPLUGIN_API USpineSkeletonAnimationComponent : public USpineSkeletonComponent { + GENERATED_BODY() + +public: + spine::AnimationState *GetAnimationState() { return state; }; + + USpineSkeletonAnimationComponent(); + + virtual void BeginPlay() override; + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + virtual void FinishDestroy() override; + + //Added functions for manual configuration + + /* Manages if this skeleton should update automatically or is paused. */ + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void SetAutoPlay(bool bInAutoPlays); + + /* Directly set the time of the current animation, will clamp to animation range. */ + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void SetPlaybackTime(float InPlaybackTime, bool bCallDelegates = true); + + // Blueprint functions + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void SetTimeScale(float timeScale); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + float GetTimeScale(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *SetAnimation(int trackIndex, FString animationName, bool loop); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *AddAnimation(int trackIndex, FString animationName, bool loop, float delay); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *SetEmptyAnimation(int trackIndex, float mixDuration); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *AddEmptyAnimation(int trackIndex, float mixDuration, float delay); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *GetCurrent(int trackIndex); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void ClearTracks(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void ClearTrack(int trackIndex); + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationStartDelegate AnimationStart; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationInterruptDelegate AnimationInterrupt; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationEventDelegate AnimationEvent; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationCompleteDelegate AnimationComplete; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationEndDelegate AnimationEnd; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationDisposeDelegate AnimationDispose; + + UPROPERTY(EditAnywhere, Category = Spine) + FString PreviewAnimation; + + UPROPERTY(EditAnywhere, Category = Spine) + FString PreviewSkin; + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetPhysicsTimeScale(float scale); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetPhysicsTimeScale(); + + // used in C event callback. Needs to be public as we can't call + // protected methods from plain old C function. + void GCTrackEntry(UTrackEntry *entry) { trackEntries.Remove(entry); } + +protected: + virtual void CheckState() override; + virtual void InternalTick(float DeltaTime, bool CallDelegates = true, bool Preview = false) override; + virtual void DisposeState() override; + + spine::AnimationState *state; + + // keep track of track entries so they won't get GCed while + // in transit within a blueprint + UPROPERTY() + TSet trackEntries; + + float physicsTimeScale; + +private: + /* If the animation should update automatically. */ + UPROPERTY() + bool bAutoPlaying; + + FString lastPreviewAnimation; + FString lastPreviewSkin; +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonComponent.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonComponent.h new file mode 100644 index 0000000..04a36e9 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonComponent.h @@ -0,0 +1,163 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +// clang-format off +#include "Components/ActorComponent.h" +#include "SpineSkeletonDataAsset.h" +#include "spine/spine.h" +#include "SpineSkeletonComponent.generated.h" +// clang-format on + +class USpineSkeletonComponent; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineBeforeUpdateWorldTransformDelegate, USpineSkeletonComponent *, skeleton); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineAfterUpdateWorldTransformDelegate, USpineSkeletonComponent *, skeleton); + +class USpineAtlasAsset; +UCLASS(ClassGroup = (Spine), meta = (BlueprintSpawnableComponent)) +class SPINEPLUGIN_API USpineSkeletonComponent : public UActorComponent { + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Spine) + USpineAtlasAsset *Atlas; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Spine) + USpineSkeletonDataAsset *SkeletonData; + + spine::Skeleton *GetSkeleton() { + CheckState(); + return skeleton; + }; + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetSkins(TArray &Skins); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool SetSkins(UPARAM(ref) TArray &SkinNames); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool SetSkin(const FString SkinName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasSkin(const FString SkinName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool SetAttachment(const FString slotName, const FString attachmentName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + FTransform GetBoneWorldTransform(const FString &BoneName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetBoneWorldPosition(const FString &BoneName, const FVector &position); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void UpdateWorldTransform(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetToSetupPose(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetBonesToSetupPose(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetSlotsToSetupPose(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetScaleX(float scaleX); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetScaleX(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetScaleY(float scaleY); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetScaleY(); + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetBones(TArray &Bones); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasBone(const FString BoneName); + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetSlots(TArray &Slots); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasSlot(const FString SlotName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetSlotColor(const FString SlotName, const FColor color); + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetAnimations(TArray &Animations); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasAnimation(FString AnimationName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetAnimationDuration(FString AnimationName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void PhysicsTranslate(float x, float y); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void PhysicsRotate(float x, float y, float degrees); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void ResetPhysicsConstraints(); + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Skeleton") + FSpineBeforeUpdateWorldTransformDelegate BeforeUpdateWorldTransform; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Skeleton") + FSpineAfterUpdateWorldTransformDelegate AfterUpdateWorldTransform; + + USpineSkeletonComponent(); + + virtual void BeginPlay() override; + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + virtual void FinishDestroy() override; + +protected: + virtual void CheckState(); + virtual void InternalTick(float DeltaTime, bool CallDelegates = true, bool Preview = false); + virtual void DisposeState(); + + spine::Skeleton *skeleton; + USpineAtlasAsset *lastAtlas = nullptr; + spine::Atlas *lastSpineAtlas = nullptr; + USpineSkeletonDataAsset *lastData = nullptr; + spine::Skin *customSkin = nullptr; +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h new file mode 100644 index 0000000..eb1a95c --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h @@ -0,0 +1,127 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +// clang-format off +#include "spine/spine.h" +#include "SpineSkeletonDataAsset.generated.h" +// clang-format on + +USTRUCT(BlueprintType, Category = "Spine") +struct SPINEPLUGIN_API FSpineAnimationStateMixData { + GENERATED_BODY(); + +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString From; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString To; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float Mix = 0; +}; + +UCLASS(BlueprintType, ClassGroup = (Spine)) +class SPINEPLUGIN_API USpineSkeletonDataAsset : public UObject { + GENERATED_BODY() + +public: + spine::SkeletonData *GetSkeletonData(spine::Atlas *Atlas); + + spine::AnimationStateData *GetAnimationStateData(spine::Atlas *atlas); + void SetMix(const FString &from, const FString &to, float mix); + float GetMix(const FString &from, const FString &to); + + FName GetSkeletonDataFileName() const; + void SetRawData(TArray &Data); + + virtual void BeginDestroy() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float DefaultMix = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray MixData; + + UPROPERTY(Transient, VisibleAnywhere) + TArray Bones; + + UPROPERTY(Transient, VisibleAnywhere) + TArray Slots; + + UPROPERTY(Transient, VisibleAnywhere) + TArray Skins; + + UPROPERTY(Transient, VisibleAnywhere) + TArray Animations; + + UPROPERTY(Transient, VisibleAnywhere) + TArray Events; + +protected: + UPROPERTY() + TArray rawData; + + UPROPERTY() + FName skeletonDataFileName; + + // These are created at runtime + struct NativeSkeletonData { + spine::SkeletonData *skeletonData; + spine::AnimationStateData *animationStateData; + }; + + TMap atlasToNativeData; + + void ClearNativeData(); + + void SetMixes(spine::AnimationStateData *animationStateData); + +#if WITH_EDITORONLY_DATA +public: + void SetSkeletonDataFileName(const FName &skeletonDataFileName); + +protected: + UPROPERTY(VisibleAnywhere, Instanced, Category = ImportSettings) + class UAssetImportData *importData = nullptr; + + virtual void PostInitProperties() override; +#if ((ENGINE_MAJOR_VERSION >= 5) && (ENGINE_MINOR_VERSION >= 4)) + virtual void GetAssetRegistryTags(FAssetRegistryTagsContext Context) const override; +#else + virtual void GetAssetRegistryTags(TArray &OutTags) const override; +#endif + + virtual void Serialize(FArchive &Ar) override; +#endif + + void LoadInfo(); +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonRendererComponent.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonRendererComponent.h new file mode 100644 index 0000000..99807f5 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonRendererComponent.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +#include "Components/ActorComponent.h" +#include "ProceduralMeshComponent.h" +#include "SpineSkeletonAnimationComponent.h" +#include "SpineSkeletonRendererComponent.generated.h" + + +UCLASS(ClassGroup = (Spine), meta = (BlueprintSpawnableComponent)) +class SPINEPLUGIN_API USpineSkeletonRendererComponent : public UProceduralMeshComponent { + GENERATED_BODY() + +public: + USpineSkeletonRendererComponent(const FObjectInitializer &ObjectInitializer); + + virtual void BeginPlay() override; + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + /* Updates this skeleton renderer using the provided skeleton animation component. */ + void UpdateRenderer(USpineSkeletonComponent *Skeleton); + + // Material Instance parents + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + UMaterialInterface *NormalBlendMaterial; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + UMaterialInterface *AdditiveBlendMaterial; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + UMaterialInterface *MultiplyBlendMaterial; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + UMaterialInterface *ScreenBlendMaterial; + + // Need to hold on to the dynamic instances, or the GC will kill us while updating them + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + TArray atlasNormalBlendMaterials; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + TArray atlasAdditiveBlendMaterials; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + TArray atlasMultiplyBlendMaterials; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + TArray atlasScreenBlendMaterials; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + float DepthOffset = 0.1f; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + FName TextureParameterName; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + FLinearColor Color = FLinearColor(1, 1, 1, 1); + + /** Whether to generate collision geometry for the skeleton, or not. */ + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + bool bCreateCollision; + + virtual void FinishDestroy() override; + +protected: + void UpdateMaterial(UTexture2D *Texture, UMaterialInstanceDynamic *&CurrentInstance, UMaterialInterface *ParentMaterial); + + void UpdateMesh(USpineSkeletonComponent *component, spine::Skeleton *Skeleton); + + void Flush(int &Idx, TArray &Vertices, TArray &Indices, TArray &Normals, TArray &Uvs, TArray &Colors, UMaterialInstanceDynamic *Material); + + spine::Vector worldVertices; + spine::SkeletonClipping clipper; + + UPROPERTY(); + TArray vertices; + UPROPERTY(); + TArray indices; + UPROPERTY(); + TArray normals; + UPROPERTY(); + TArray uvs; + UPROPERTY(); + TArray colors; +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineWidget.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineWidget.h new file mode 100644 index 0000000..b6a3ed6 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineWidget.h @@ -0,0 +1,287 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#pragma once + +// clang-format off +#include "Runtime/UMG/Public/UMG.h" +#include "SpineSkeletonDataAsset.h" +#include "SpineSkeletonAnimationComponent.h" +#include "spine/spine.h" +#include "SpineWidget.generated.h" +// clang-format on + +class SSpineWidget; +class USpineWidget; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineWidgetBeforeUpdateWorldTransformDelegate, USpineWidget *, skeleton); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSpineWidgetAfterUpdateWorldTransformDelegate, USpineWidget *, skeleton); + +UCLASS(ClassGroup = (Spine), meta = (BlueprintSpawnableComponent)) +class SPINEPLUGIN_API USpineWidget : public UWidget { + GENERATED_UCLASS_BODY() + +public: + virtual void ReleaseSlateResources(bool bReleaseChildren) override; + virtual void SynchronizeProperties() override; +#if WITH_EDITOR + virtual const FText GetPaletteCategory() override; +#endif + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Spine) + FString InitialSkin; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Spine) + USpineAtlasAsset *Atlas; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Spine) + USpineSkeletonDataAsset *SkeletonData; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadOnly) + UMaterialInterface *NormalBlendMaterial; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadOnly) + UMaterialInterface *AdditiveBlendMaterial; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadOnly) + UMaterialInterface *MultiplyBlendMaterial; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadOnly) + UMaterialInterface *ScreenBlendMaterial; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + FName TextureParameterName; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + float DepthOffset = 0.1f; + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadWrite) + FLinearColor Color = FLinearColor(1, 1, 1, 1); + + UPROPERTY(Category = Spine, EditAnywhere, BlueprintReadOnly) + FSlateBrush Brush; + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetSkins(TArray &Skins); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool SetSkin(const FString SkinName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool SetSkins(UPARAM(ref) TArray &SkinNames); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasSkin(const FString SkinName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool SetAttachment(const FString slotName, const FString attachmentName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void UpdateWorldTransform(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetToSetupPose(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetBonesToSetupPose(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetSlotsToSetupPose(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetScaleX(float scaleX); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetScaleX(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetScaleY(float scaleY); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetScaleY(); + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetBones(TArray &Bones); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasBone(const FString BoneName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + FTransform GetBoneTransform(const FString &BoneName); + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetSlots(TArray &Slots); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasSlot(const FString SlotName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetSlotColor(const FString SlotName, const FColor SlotColor); + + UFUNCTION(BlueprintPure, Category = "Components|Spine|Skeleton") + void GetAnimations(TArray &Animations); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + bool HasAnimation(FString AnimationName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetAnimationDuration(FString AnimationName); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void PhysicsTranslate(float x, float y); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void PhysicsRotate(float x, float y, float degrees); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void ResetPhysicsConstraints(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + void SetPhysicsTimeScale(float scale); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Skeleton") + float GetPhysicsTimeScale(); + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Skeleton") + FSpineWidgetBeforeUpdateWorldTransformDelegate BeforeUpdateWorldTransform; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Skeleton") + FSpineWidgetAfterUpdateWorldTransformDelegate AfterUpdateWorldTransform; + + /* Manages if this skeleton should update automatically or is paused. */ + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void SetAutoPlay(bool bInAutoPlays); + + /* Directly set the time of the current animation, will clamp to animation range. */ + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void SetPlaybackTime(float InPlaybackTime, bool bCallDelegates = true); + + // Blueprint functions + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void SetTimeScale(float timeScale); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + float GetTimeScale(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *SetAnimation(int trackIndex, FString animationName, bool loop); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *AddAnimation(int trackIndex, FString animationName, bool loop, float delay); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *SetEmptyAnimation(int trackIndex, float mixDuration); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *AddEmptyAnimation(int trackIndex, float mixDuration, float delay); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + UTrackEntry *GetCurrent(int trackIndex); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void ClearTracks(); + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void ClearTrack(int trackIndex); + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationStartDelegate AnimationStart; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationInterruptDelegate AnimationInterrupt; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationEventDelegate AnimationEvent; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationCompleteDelegate AnimationComplete; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationEndDelegate AnimationEnd; + + UPROPERTY(BlueprintAssignable, Category = "Components|Spine|Animation") + FSpineAnimationDisposeDelegate AnimationDispose; + + UFUNCTION(BlueprintCallable, Category = "Components|Spine|Animation") + void Tick(float DeltaTime, bool CallDelegates = true); + + virtual void FinishDestroy() override; + + // used in C event callback. Needs to be public as we can't call + // protected methods from plain old C function. + void GCTrackEntry(UTrackEntry *entry) { trackEntries.Remove(entry); } + +protected: + friend class SSpineWidget; + + virtual TSharedRef RebuildWidget() override; + virtual void CheckState(); + virtual void DisposeState(); + + TSharedPtr slateWidget; + + spine::Skeleton *skeleton; + spine::AnimationState *state; + USpineAtlasAsset *lastAtlas = nullptr; + spine::Atlas *lastSpineAtlas = nullptr; + USpineSkeletonDataAsset *lastData = nullptr; + spine::Skin *customSkin = nullptr; + float physicsTimeScale; + + // Need to hold on to the dynamic instances, or the GC will kill us while updating them + UPROPERTY() + TArray atlasNormalBlendMaterials; + TMap pageToNormalBlendMaterial; + + UPROPERTY() + TArray atlasAdditiveBlendMaterials; + TMap pageToAdditiveBlendMaterial; + + UPROPERTY() + TArray atlasMultiplyBlendMaterials; + TMap pageToMultiplyBlendMaterial; + + UPROPERTY() + TArray atlasScreenBlendMaterials; + TMap pageToScreenBlendMaterial; + + spine::Vector worldVertices; + spine::SkeletonClipping clipper; + + // keep track of track entries so they won't get GCed while + // in transit within a blueprint + UPROPERTY() + TSet trackEntries; + +private: + /* If the animation should update automatically. */ + UPROPERTY() + bool bAutoPlaying; + bool bSkinInitialized = false; +}; diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Animation.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Animation.h new file mode 100644 index 0000000..7c9355c --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Animation.h @@ -0,0 +1,131 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Animation_h +#define Spine_Animation_h + +#include +#include +#include +#include +#include +#include +#include + +namespace spine { + class Timeline; + + class Skeleton; + + class Event; + + class AnimationState; + + class SP_API Animation : public SpineObject { + friend class AnimationState; + + friend class TrackEntry; + + friend class AnimationStateData; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class RotateTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + friend class TwoColorTimeline; + + public: + Animation(const String &name, Vector &timelines, float duration); + + ~Animation(); + + /// Applies all the animation's timelines to the specified skeleton. + /// See also Timeline::apply(Skeleton&, float, float, Vector, float, MixPose, MixDirection) + void apply(Skeleton &skeleton, float lastTime, float time, bool loop, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction); + + const String &getName(); + + Vector &getTimelines(); + + bool hasTimeline(Vector &ids); + + float getDuration(); + + void setDuration(float inValue); + + /// @param target After the first and before the last entry. + static int search(Vector &values, float target); + + static int search(Vector &values, float target, int step); + private: + Vector _timelines; + HashMap _timelineIds; + float _duration; + String _name; + }; +} + +#endif /* Spine_Animation_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AnimationState.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AnimationState.h new file mode 100644 index 0000000..501e722 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AnimationState.h @@ -0,0 +1,521 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AnimationState_h +#define Spine_AnimationState_h + +#include +#include +#include +#include +#include +#include +#include +#include "Slot.h" + +#ifdef SPINE_USE_STD_FUNCTION +#include +#endif + +namespace spine { + enum EventType { + EventType_Start = 0, + EventType_Interrupt, + EventType_End, + EventType_Complete, + EventType_Dispose, + EventType_Event + }; + + class AnimationState; + + class TrackEntry; + + class Animation; + + class Event; + + class AnimationStateData; + + class Skeleton; + + class RotateTimeline; + + class AttachmentTimeline; + +#ifdef SPINE_USE_STD_FUNCTION + typedef std::function AnimationStateListener; +#else + + typedef void (*AnimationStateListener)(AnimationState *state, EventType type, TrackEntry *entry, Event *event); + +#endif + + /// Abstract class to inherit from to create a callback object + class SP_API AnimationStateListenerObject { + public: + AnimationStateListenerObject() {}; + + virtual ~AnimationStateListenerObject() {}; + public: + /// The callback function to be called + virtual void callback(AnimationState *state, EventType type, TrackEntry *entry, Event *event) = 0; + }; + + /// State for the playback of an animation + class SP_API TrackEntry : public SpineObject, public HasRendererObject { + friend class EventQueue; + + friend class AnimationState; + + public: + TrackEntry(); + + virtual ~TrackEntry(); + + /// The index of the track where this entry is either current or queued. + int getTrackIndex(); + + /// The animation to apply for this track entry. + Animation *getAnimation(); + + TrackEntry *getPrevious(); + + /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. + bool getLoop(); + + void setLoop(bool inValue); + + /// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead + /// of being mixed out. + /// + /// When mixing between animations that key the same property, if a lower track also keys that property then the value will + /// briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% + /// while the second animation mixes from 0% to 100%. Setting holdPrevious to true applies the first animation + /// at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which + /// keys the property, only when a higher track also keys the property. + /// + /// Snapping will occur if holdPrevious is true and this animation does not key all the same properties as the + /// previous animation. + bool getHoldPrevious(); + + void setHoldPrevious(bool inValue); + + bool getReverse(); + + void setReverse(bool inValue); + + bool getShortestRotation(); + + void setShortestRotation(bool inValue); + + /// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing + /// the track time. When a track entry is queued, delay is the time from the start of the previous animation to when the + /// track entry will become the current track entry. + float getDelay(); + + void setDelay(float inValue); + + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// TrackEntry.AnimationTime. The track time can be set to start the animation at a time other than 0, without affecting looping. + float getTrackTime(); + + void setTrackTime(float inValue); + + /// The track time in seconds when this animation will be removed from the track. Defaults to the animation duration for + /// non-looping animations and to int.MaxValue for looping animations. If the track end time is reached and no + /// other animations are queued for playback, and mixing from any previous animations is complete, properties keyed by the animation, + /// are set to the setup pose and the track is cleared. + /// + /// It may be desired to use AnimationState.addEmptyAnimation(int, float, float) to mix the properties back to the + /// setup pose over time, rather than have it happen instantly. + float getTrackEnd(); + + void setTrackEnd(float inValue); + + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the animation start time, it often makes sense to set TrackEntry.AnimationLast to the same value to + /// prevent timeline keys before the start time from triggering. + float getAnimationStart(); + + void setAnimationStart(float inValue); + + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to TrackEntry.AnimationStart at this time. Defaults to the animation duration. + float getAnimationEnd(); + + void setAnimationEnd(float inValue); + + /// The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this + /// animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time + /// (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied. + float getAnimationLast(); + + void setAnimationLast(float inValue); + + /// Uses TrackEntry.TrackTime to compute the animation time between TrackEntry.AnimationStart. and + /// TrackEntry.AnimationEnd. When the track time is 0, the animation time is equal to the animation start time. + float getAnimationTime(); + + /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or + /// faster. Defaults to 1. + float getTimeScale(); + + void setTimeScale(float inValue); + + /// Values less than 1 mix this animation with the last skeleton pose. Defaults to 1, which overwrites the last skeleton pose with + /// this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense + /// to use alpha on track 0 if the skeleton pose is from the last frame render. + float getAlpha(); + + void setAlpha(float inValue); + + /// + /// When the mix percentage (mix time / mix duration) is less than the event threshold, event timelines for the animation + /// being mixed out will be applied. Defaults to 0, so event timelines are not applied for an animation being mixed out. + float getEventThreshold(); + + void setEventThreshold(float inValue); + + /// When the mix percentage (mix time / mix duration) is less than the attachment threshold, attachment timelines for the + /// animation being mixed out will be applied. Defaults to 0, so attachment timelines are not applied for an animation being + /// mixed out. + float getMixAttachmentThreshold(); + + void setMixAttachmentThreshold(float inValue); + + /// When getAlpha() is greater than alphaAttachmentThreshold, attachment timelines are applied. + /// Defaults to 0, so attachment timelines are always applied. */ + float getAlphaAttachmentThreshold(); + + void setAlphaAttachmentThreshold(float inValue); + + /// When the mix percentage (mix time / mix duration) is less than the draw order threshold, draw order timelines for the + /// animation being mixed out will be applied. Defaults to 0, so draw order timelines are not applied for an animation being + /// mixed out. + float getMixDrawOrderThreshold(); + + void setMixDrawOrderThreshold(float inValue); + + /// The animation queued to start after this animation, or NULL. + TrackEntry *getNext(); + + /// Returns true if at least one loop has been completed. + bool isComplete(); + + /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than + /// TrackEntry.MixDuration when the mix is complete. + float getMixTime(); + + void setMixTime(float inValue); + + /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + /// AnimationStateData based on the animation before this animation (if any). + /// + /// The mix duration can be set manually rather than use the value from AnimationStateData.GetMix. + /// In that case, the mixDuration must be set before AnimationState.update(float) is next called. + /// + /// When using AnimationState::addAnimation(int, Animation, bool, float) with a delay + /// less than or equal to 0, note the Delay is set using the mix duration from the AnimationStateData + float getMixDuration(); + + void setMixDuration(float inValue); + + void setMixDuration(float mixDuration, float delay); + + MixBlend getMixBlend(); + + void setMixBlend(MixBlend blend); + + /// The track entry for the previous animation when mixing from the previous animation to this animation, or NULL if no + /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a double linked list with MixingTo. + TrackEntry *getMixingFrom(); + + /// The track entry for the next animation when mixing from this animation, or NULL if no mixing is currently occuring. + /// When mixing from multiple animations, MixingTo makes up a double linked list with MixingFrom. + TrackEntry *getMixingTo(); + + /// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the + /// long way around when using alpha and starting animations on other tracks. + /// + /// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around. + /// The two rotations likely change over time, so which direction is the short or long way also changes. + /// If the short way was always chosen, bones would flip to the other side when that direction became the long way. + /// TrackEntry chooses the short way the first time it is applied and remembers that direction. + void resetRotationDirections(); + + float getTrackComplete(); + + void setListener(AnimationStateListener listener); + + void setListener(AnimationStateListenerObject *listener); + + /// Returns true if this track entry has been applied at least once. + /// + /// See AnimationState::apply(Skeleton). + bool wasApplied(); + + /// Returns true if there is a getNext() track entry that is ready to become the current track entry during the + /// next AnimationState::update(float)} + bool isNextReady () { + return _next != NULL && _nextTrackLast - _next->_delay >= 0; + } + + private: + Animation *_animation; + TrackEntry *_previous; + TrackEntry *_next; + TrackEntry *_mixingFrom; + TrackEntry *_mixingTo; + int _trackIndex; + + bool _loop, _holdPrevious, _reverse, _shortestRotation; + float _eventThreshold, _mixAttachmentThreshold, _alphaAttachmentThreshold, _mixDrawOrderThreshold; + float _animationStart, _animationEnd, _animationLast, _nextAnimationLast; + float _delay, _trackTime, _trackLast, _nextTrackLast, _trackEnd, _timeScale; + float _alpha, _mixTime, _mixDuration, _interruptAlpha, _totalAlpha; + MixBlend _mixBlend; + Vector _timelineMode; + Vector _timelineHoldMix; + Vector _timelinesRotation; + AnimationStateListener _listener; + AnimationStateListenerObject *_listenerObject; + + void reset(); + }; + + class SP_API EventQueueEntry : public SpineObject { + friend class EventQueue; + + public: + EventType _type; + TrackEntry *_entry; + Event *_event; + + EventQueueEntry(EventType eventType, TrackEntry *trackEntry, Event *event = NULL); + }; + + class SP_API EventQueue : public SpineObject { + friend class AnimationState; + + private: + Vector _eventQueueEntries; + AnimationState &_state; + bool _drainDisabled; + + static EventQueue *newEventQueue(AnimationState &state); + + static EventQueueEntry newEventQueueEntry(EventType eventType, TrackEntry *entry, Event *event = NULL); + + EventQueue(AnimationState &state); + + ~EventQueue(); + + void start(TrackEntry *entry); + + void interrupt(TrackEntry *entry); + + void end(TrackEntry *entry); + + void dispose(TrackEntry *entry); + + void complete(TrackEntry *entry); + + void event(TrackEntry *entry, Event *event); + + /// Raises all events in the queue and drains the queue. + void drain(); + }; + + class SP_API AnimationState : public SpineObject, public HasRendererObject { + friend class TrackEntry; + + friend class EventQueue; + + public: + explicit AnimationState(AnimationStateData *data); + + ~AnimationState(); + + /// Increments the track entry times, setting queued animations as current if needed + /// @param delta delta time + void update(float delta); + + /// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the + /// animation state can be applied to multiple skeletons to pose them identically. + bool apply(Skeleton &skeleton); + + /// Removes all animations from all tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTracks(); + + /// Removes all animations from the tracks, leaving skeletons in their previous pose. + /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + void clearTrack(size_t trackIndex); + + /// Sets an animation by name. setAnimation(int, Animation, bool) + TrackEntry *setAnimation(size_t trackIndex, const String &animationName, bool loop); + + /// Sets the current animation for a track, discarding any queued animations. + /// @param loop If true, the animation will repeat. + /// If false, it will not, instead its last frame is applied if played beyond its duration. + /// In either case TrackEntry.TrackEnd determines when the track is cleared. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose. + TrackEntry *setAnimation(size_t trackIndex, Animation *animation, bool loop); + + /// Queues an animation by name. + /// addAnimation(int, Animation, bool, float) + TrackEntry *addAnimation(size_t trackIndex, const String &animationName, bool loop, float delay); + + /// Adds an animation to be played delay seconds after the current or last queued animation + /// for a track. If the track is empty, it is equivalent to calling setAnimation. + /// @param delay + /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + /// + /// @return A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after AnimationState.Dispose + TrackEntry *addAnimation(size_t trackIndex, Animation *animation, bool loop, float delay); + + /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + TrackEntry *setEmptyAnimation(size_t trackIndex, float mixDuration); + + /// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the + /// specified mix duration. + /// @return + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after AnimationState.Dispose. + /// + /// @param trackIndex Track number. + /// @param mixDuration Mix duration. + /// @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation + /// duration of the previous track minus any mix duration plus the negative delay. + TrackEntry *addEmptyAnimation(size_t trackIndex, float mixDuration, float delay); + + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + void setEmptyAnimations(float mixDuration); + + /// @return The track entry for the animation currently playing on the track, or NULL if no animation is currently playing. + TrackEntry *getCurrent(size_t trackIndex); + + AnimationStateData *getData(); + + /// A list of tracks that have animations, which may contain NULLs. + Vector &getTracks(); + + float getTimeScale(); + + void setTimeScale(float inValue); + + void setListener(AnimationStateListener listener); + + void setListener(AnimationStateListenerObject *listener); + + void disableQueue(); + + void enableQueue(); + + void setManualTrackEntryDisposal(bool inValue); + + bool getManualTrackEntryDisposal(); + + void disposeTrackEntry(TrackEntry *entry); + + private: + static const int Subsequent = 0; + static const int First = 1; + static const int HoldSubsequent = 2; + static const int HoldFirst = 3; + static const int HoldMix = 4; + + static const int Setup = 1; + static const int Current = 2; + + AnimationStateData *_data; + + Pool _trackEntryPool; + Vector _tracks; + Vector _events; + EventQueue *_queue; + + HashMap _propertyIDs; + bool _animationsChanged; + + AnimationStateListener _listener; + AnimationStateListenerObject *_listenerObject; + + int _unkeyedState; + + float _timeScale; + + bool _manualTrackEntryDisposal; + + static Animation *getEmptyAnimation(); + + static void + applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleton &skeleton, float time, float alpha, MixBlend pose, + Vector &timelinesRotation, size_t i, bool firstFrame); + + void applyAttachmentTimeline(AttachmentTimeline *attachmentTimeline, Skeleton &skeleton, float animationTime, + MixBlend pose, bool firstFrame); + + /// Returns true when all mixing from entries are complete. + bool updateMixingFrom(TrackEntry *to, float delta); + + float applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBlend currentPose); + + void queueEvents(TrackEntry *entry, float animationTime); + + /// Sets the active TrackEntry for a given track number. + void setCurrent(size_t index, TrackEntry *current, bool interrupt); + + /// Removes the next entry and all entries after it for the specified entry. */ + void clearNext(TrackEntry *entry); + + TrackEntry *expandToIndex(size_t index); + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// @param last May be NULL. + TrackEntry *newTrackEntry(size_t trackIndex, Animation *animation, bool loop, TrackEntry *last); + + void animationsChanged(); + + void computeHold(TrackEntry *entry); + + void setAttachment(Skeleton &skeleton, spine::Slot &slot, const String &attachmentName, bool attachments); + }; +} + +#endif /* Spine_AnimationState_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AnimationStateData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AnimationStateData.h new file mode 100644 index 0000000..a315e76 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AnimationStateData.h @@ -0,0 +1,90 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AnimationStateData_h +#define Spine_AnimationStateData_h + +#include +#include +#include + +#include + +namespace spine { + class SkeletonData; + + class Animation; + + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + class SP_API AnimationStateData : public SpineObject { + friend class AnimationState; + + public: + explicit AnimationStateData(SkeletonData *skeletonData); + + /// The SkeletonData to look up animations when they are specified by name. + SkeletonData *getSkeletonData(); + + /// The mix duration to use when no mix duration has been specifically defined between two animations. + float getDefaultMix(); + + void setDefaultMix(float inValue); + + /// Sets a mix duration by animation names. + void setMix(const String &fromName, const String &toName, float duration); + + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + void setMix(Animation *from, Animation *to, float duration); + + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + float getMix(Animation *from, Animation *to); + + /// Removes all mixes and sets the default mix to 0. + void clear(); + + private: + class AnimationPair : public SpineObject { + public: + Animation *_a1; + Animation *_a2; + + explicit AnimationPair(Animation *a1 = NULL, Animation *a2 = NULL); + + bool operator==(const AnimationPair &other) const; + }; + + SkeletonData *_skeletonData; + float _defaultMix; + HashMap _animationToMixTime; + }; +} + +#endif /* Spine_AnimationStateData_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Atlas.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Atlas.h new file mode 100644 index 0000000..f6c7931 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Atlas.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Atlas_h +#define Spine_Atlas_h + +#include +#include +#include +#include +#include +#include "TextureRegion.h" + +namespace spine { + enum Format { + Format_Alpha, + Format_Intensity, + Format_LuminanceAlpha, + Format_RGB565, + Format_RGBA4444, + Format_RGB888, + Format_RGBA8888 + }; + + // Our TextureFilter collides with UE4's TextureFilter in unity builds. We rename + // TextureFilter to SpineTextureFilter in UE4. +#ifdef SPINE_UE4 + #define TEXTURE_FILTER_ENUM SpineTextureFilter +#else + #define TEXTURE_FILTER_ENUM TextureFilter +#endif + + enum TEXTURE_FILTER_ENUM { + TextureFilter_Unknown, + TextureFilter_Nearest, + TextureFilter_Linear, + TextureFilter_MipMap, + TextureFilter_MipMapNearestNearest, + TextureFilter_MipMapLinearNearest, + TextureFilter_MipMapNearestLinear, + TextureFilter_MipMapLinearLinear + }; + + enum TextureWrap { + TextureWrap_MirroredRepeat, + TextureWrap_ClampToEdge, + TextureWrap_Repeat + }; + + class SP_API AtlasPage : public SpineObject { + public: + String name; + String texturePath; + Format format; + TEXTURE_FILTER_ENUM minFilter; + TEXTURE_FILTER_ENUM magFilter; + TextureWrap uWrap; + TextureWrap vWrap; + int width, height; + bool pma; + int index; + void *texture; + + explicit AtlasPage(const String &inName) : name(inName), format(Format_RGBA8888), + minFilter(TextureFilter_Nearest), + magFilter(TextureFilter_Nearest), uWrap(TextureWrap_ClampToEdge), + vWrap(TextureWrap_ClampToEdge), width(0), height(0), pma(false), index(0), texture(NULL) { + } + }; + + class SP_API AtlasRegion : public TextureRegion { + public: + AtlasPage *page; + String name; + int index; + int x, y; + Vector splits; + Vector pads; + Vector names; + Vector values; + }; + + class TextureLoader; + + class SP_API Atlas : public SpineObject { + public: + Atlas(const String &path, TextureLoader *textureLoader, bool createTexture = true); + + Atlas(const char *data, int length, const char *dir, TextureLoader *textureLoader, bool createTexture = true); + + ~Atlas(); + + void flipV(); + + /// Returns the first region found with the specified name. This method uses String comparison to find the region, so the result + /// should be cached rather than calling this method multiple times. + /// @return The region, or NULL. + AtlasRegion *findRegion(const String &name); + + Vector &getPages(); + + Vector &getRegions(); + + private: + Vector _pages; + Vector _regions; + TextureLoader *_textureLoader; + + void load(const char *begin, int length, const char *dir, bool createTexture); + }; +} + +#endif /* Spine_Atlas_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AtlasAttachmentLoader.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AtlasAttachmentLoader.h new file mode 100644 index 0000000..8511167 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AtlasAttachmentLoader.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AtlasAttachmentLoader_h +#define Spine_AtlasAttachmentLoader_h + +#include +#include +#include + + +namespace spine { + class Atlas; + + class AtlasRegion; + + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data about Loading Skeleton Data in the Spine Runtimes Guide. + class SP_API AtlasAttachmentLoader : public AttachmentLoader { + public: + RTTI_DECL + + explicit AtlasAttachmentLoader(Atlas *atlas); + + virtual RegionAttachment *newRegionAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence); + + virtual MeshAttachment *newMeshAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence); + + virtual BoundingBoxAttachment *newBoundingBoxAttachment(Skin &skin, const String &name); + + virtual PathAttachment *newPathAttachment(Skin &skin, const String &name); + + virtual PointAttachment *newPointAttachment(Skin &skin, const String &name); + + virtual ClippingAttachment *newClippingAttachment(Skin &skin, const String &name); + + virtual void configureAttachment(Attachment *attachment); + + AtlasRegion *findRegion(const String &name); + + private: + Atlas *_atlas; + }; +} + +#endif /* Spine_AtlasAttachmentLoader_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Attachment.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Attachment.h new file mode 100644 index 0000000..ae72b18 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Attachment.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Attachment_h +#define Spine_Attachment_h + +#include +#include +#include + +namespace spine { + class SP_API Attachment : public SpineObject { + RTTI_DECL + + public: + explicit Attachment(const String &name); + + virtual ~Attachment(); + + const String &getName() const; + + virtual Attachment *copy() = 0; + + int getRefCount(); + + void reference(); + + void dereference(); + + private: + const String _name; + int _refCount; + }; +} + +#endif /* Spine_Attachment_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AttachmentLoader.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AttachmentLoader.h new file mode 100644 index 0000000..9f02302 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AttachmentLoader.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AttachmentLoader_h +#define Spine_AttachmentLoader_h + +#include +#include +#include + +namespace spine { + class Skin; + + class Attachment; + + class RegionAttachment; + + class MeshAttachment; + + class BoundingBoxAttachment; + + class PathAttachment; + + class PointAttachment; + + class ClippingAttachment; + + class Sequence; + + class SP_API AttachmentLoader : public SpineObject { + public: + RTTI_DECL + + AttachmentLoader(); + + virtual ~AttachmentLoader(); + + /// @return May be NULL to not load any attachment. + virtual RegionAttachment *newRegionAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) = 0; + + /// @return May be NULL to not load any attachment. + virtual MeshAttachment *newMeshAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) = 0; + + /// @return May be NULL to not load any attachment. + virtual BoundingBoxAttachment *newBoundingBoxAttachment(Skin &skin, const String &name) = 0; + + /// @return May be NULL to not load any attachment + virtual PathAttachment *newPathAttachment(Skin &skin, const String &name) = 0; + + virtual PointAttachment *newPointAttachment(Skin &skin, const String &name) = 0; + + virtual ClippingAttachment *newClippingAttachment(Skin &skin, const String &name) = 0; + + virtual void configureAttachment(Attachment *attachment) = 0; + }; +} + +#endif /* Spine_AttachmentLoader_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AttachmentTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AttachmentTimeline.h new file mode 100644 index 0000000..a42280f --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AttachmentTimeline.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AttachmentTimeline_h +#define Spine_AttachmentTimeline_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + + class Skeleton; + + class Slot; + + class Event; + + class SP_API AttachmentTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit AttachmentTimeline(size_t frameCount, int slotIndex); + + virtual ~AttachmentTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, const String &attachmentName); + + Vector &getAttachmentNames(); + + int getSlotIndex() { return _slotIndex; } + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + Vector _attachmentNames; + + void setAttachment(Skeleton &skeleton, Slot &slot, String *attachmentName); + }; +} + +#endif /* Spine_AttachmentTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AttachmentType.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AttachmentType.h new file mode 100644 index 0000000..8b1a7ed --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/AttachmentType.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_AttachmentType_h +#define Spine_AttachmentType_h + +namespace spine { + enum AttachmentType { + AttachmentType_Region, + AttachmentType_Boundingbox, + AttachmentType_Mesh, + AttachmentType_Linkedmesh, + AttachmentType_Path, + AttachmentType_Point, + AttachmentType_Clipping + }; +} + +#endif /* Spine_AttachmentType_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BlendMode.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BlendMode.h new file mode 100644 index 0000000..f34e95b --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BlendMode.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BlendMode_h +#define Spine_BlendMode_h + +namespace spine { + enum BlendMode { + BlendMode_Normal = 0, + BlendMode_Additive, + BlendMode_Multiply, + BlendMode_Screen + }; +} + +#endif /* Spine_BlendMode_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BlockAllocator.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BlockAllocator.h new file mode 100644 index 0000000..749a69c --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BlockAllocator.h @@ -0,0 +1,114 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BlockAllocator_h +#define Spine_BlockAllocator_h + +#include +#include +#include +#include +#include + +namespace spine { + struct Block { + int size; + int allocated; + uint8_t *memory; + + int free() { + return size - allocated; + } + + bool canFit(int numBytes) { + return free() >= numBytes; + } + + uint8_t *allocate(int numBytes) { + uint8_t *ptr = memory + allocated; + allocated += numBytes; + return ptr; + } + }; + + class BlockAllocator : public SpineObject { + int initialBlockSize; + Vector blocks; + + public: + BlockAllocator(int initialBlockSize) : initialBlockSize(initialBlockSize) { + blocks.add(newBlock(initialBlockSize)); + } + + ~BlockAllocator() { + for (int i = 0, n = (int) blocks.size(); i < n; i++) { + SpineExtension::free(blocks[i].memory, __FILE__, __LINE__); + } + } + + template + T *allocate(size_t num) { + return (T *) _allocate((int) (sizeof(T) * num)); + } + + void compress() { + if (blocks.size() == 1) { + blocks[0].allocated = 0; + return; + } + int totalSize = 0; + for (int i = 0, n = (int)blocks.size(); i < n; i++) { + totalSize += blocks[i].size; + SpineExtension::free(blocks[i].memory, __FILE__, __LINE__); + } + blocks.clear(); + blocks.add(newBlock(totalSize)); + } + + private: + void *_allocate(int numBytes) { + // 16-byte align allocations + int alignedNumBytes = numBytes + (numBytes % 16 != 0 ? 16 - (numBytes % 16) : 0); + Block *block = &blocks[blocks.size() - 1]; + if (!block->canFit(alignedNumBytes)) { + blocks.add(newBlock(MathUtil::max(initialBlockSize, alignedNumBytes))); + block = &blocks[blocks.size() - 1]; + } + return block->allocate(alignedNumBytes); + } + + Block newBlock(int numBytes) { + Block block = {MathUtil::max(initialBlockSize, numBytes), 0, nullptr}; + block.memory = SpineExtension::alloc(block.size, __FILE__, __LINE__); + return block; + } + }; +} + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Bone.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Bone.h new file mode 100644 index 0000000..845fc58 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Bone.h @@ -0,0 +1,286 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Bone_h +#define Spine_Bone_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class Skeleton; + +/// Stores a bone's current pose. +/// +/// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a +/// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a +/// constraint or application code modifies the world transform after it was computed from the local transform. + class SP_API Bone : public Updatable { + friend class AnimationState; + + friend class RotateTimeline; + + friend class IkConstraint; + + friend class TransformConstraint; + + friend class VertexAttachment; + + friend class PathConstraint; + + friend class PhysicsConstraint; + + friend class Skeleton; + + friend class RegionAttachment; + + friend class PointAttachment; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class ScaleTimeline; + + friend class ScaleXTimeline; + + friend class ScaleYTimeline; + + friend class ShearTimeline; + + friend class ShearXTimeline; + + friend class ShearYTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + friend class InheritTimeline; + + RTTI_DECL + + public: + static void setYDown(bool inValue); + + static bool isYDown(); + + /// @param parent May be NULL. + Bone(BoneData &data, Skeleton &skeleton, Bone *parent = NULL); + + /// Same as updateWorldTransform. This method exists for Bone to implement Spine::Updatable. + virtual void update(Physics physics); + + /// Computes the world transform using the parent bone and this bone's local transform. + void updateWorldTransform(); + + /// Computes the world transform using the parent bone and the specified local transform. + void + updateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY); + + /// Computes the individual applied transform values from the world transform. This can be useful to perform processing using + /// the applied transform after the world transform has been modified directly (eg, by a constraint).. + /// + /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. + void updateAppliedTransform(); + + void setToSetupPose(); + + void worldToLocal(float worldX, float worldY, float &outLocalX, float &outLocalY); + + void worldToParent(float worldX, float worldY, float &outParentX, float &outParentY); + + void localToWorld(float localX, float localY, float &outWorldX, float &outWorldY); + + void parentToWorld(float worldX, float worldY, float &outX, float &outY); + + float worldToLocalRotation(float worldRotation); + + float localToWorldRotation(float localRotation); + + /// Rotates the world transform the specified amount and sets isAppliedValid to false. + /// @param degrees Degrees. + void rotateWorld(float degrees); + + float getWorldToLocalRotationX(); + + float getWorldToLocalRotationY(); + + BoneData &getData(); + + Skeleton &getSkeleton(); + + Bone *getParent(); + + Vector &getChildren(); + + /// The local X translation. + float getX(); + + void setX(float inValue); + + /// The local Y translation. + float getY(); + + void setY(float inValue); + + /// The local rotation. + float getRotation(); + + void setRotation(float inValue); + + /// The local scaleX. + float getScaleX(); + + void setScaleX(float inValue); + + /// The local scaleY. + float getScaleY(); + + void setScaleY(float inValue); + + /// The local shearX. + float getShearX(); + + void setShearX(float inValue); + + /// The local shearY. + float getShearY(); + + void setShearY(float inValue); + + /// The rotation, as calculated by any constraints. + float getAppliedRotation(); + + void setAppliedRotation(float inValue); + + /// The applied local x translation. + float getAX(); + + void setAX(float inValue); + + /// The applied local y translation. + float getAY(); + + void setAY(float inValue); + + /// The applied local scaleX. + float getAScaleX(); + + void setAScaleX(float inValue); + + /// The applied local scaleY. + float getAScaleY(); + + void setAScaleY(float inValue); + + /// The applied local shearX. + float getAShearX(); + + void setAShearX(float inValue); + + /// The applied local shearY. + float getAShearY(); + + void setAShearY(float inValue); + + float getA(); + + void setA(float inValue); + + float getB(); + + void setB(float inValue); + + float getC(); + + void setC(float inValue); + + float getD(); + + void setD(float inValue); + + float getWorldX(); + + void setWorldX(float inValue); + + float getWorldY(); + + void setWorldY(float inValue); + + float getWorldRotationX(); + + float getWorldRotationY(); + + /// Returns the magnitide (always positive) of the world scale X. + float getWorldScaleX(); + + /// Returns the magnitide (always positive) of the world scale Y. + float getWorldScaleY(); + + bool isActive(); + + void setActive(bool inValue); + + Inherit getInherit() { return _inherit; } + + void setInherit(Inherit inValue) { _inherit = inValue; } + + private: + static bool yDown; + + BoneData &_data; + Skeleton &_skeleton; + Bone *_parent; + Vector _children; + float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY; + float _ax, _ay, _arotation, _ascaleX, _ascaleY, _ashearX, _ashearY; + float _a, _b, _worldX; + float _c, _d, _worldY; + bool _sorted; + bool _active; + Inherit _inherit; + }; +} + +#endif /* Spine_Bone_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BoneData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BoneData.h new file mode 100644 index 0000000..eb53487 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BoneData.h @@ -0,0 +1,150 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BoneData_h +#define Spine_BoneData_h + +#include +#include +#include +#include + +namespace spine { + class SP_API BoneData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + friend class RotateTimeline; + + friend class ScaleTimeline; + + friend class ScaleXTimeline; + + friend class ScaleYTimeline; + + friend class ShearTimeline; + + friend class ShearXTimeline; + + friend class ShearYTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + public: + BoneData(int index, const String &name, BoneData *parent = NULL); + + /// The index of the bone in Skeleton.Bones + int getIndex(); + + /// The name of the bone, which is unique within the skeleton. + const String &getName(); + + /// May be NULL. + BoneData *getParent(); + + float getLength(); + + void setLength(float inValue); + + /// Local X translation. + float getX(); + + void setX(float inValue); + + /// Local Y translation. + float getY(); + + void setY(float inValue); + + /// Local rotation. + float getRotation(); + + void setRotation(float inValue); + + /// Local scaleX. + float getScaleX(); + + void setScaleX(float inValue); + + /// Local scaleY. + float getScaleY(); + + void setScaleY(float inValue); + + /// Local shearX. + float getShearX(); + + void setShearX(float inValue); + + /// Local shearY. + float getShearY(); + + void setShearY(float inValue); + + /// The transform mode for how parent world transforms affect this bone. + Inherit getInherit(); + + void setInherit(Inherit inValue); + + bool isSkinRequired(); + + void setSkinRequired(bool inValue); + + Color &getColor(); + + const String &getIcon(); + + void setIcon(const String &icon); + + bool isVisible(); + + void setVisible(bool inValue); + + private: + const int _index; + const String _name; + BoneData *_parent; + float _length; + float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY; + Inherit _inherit; + bool _skinRequired; + Color _color; + String _icon; + bool _visible; + }; +} + +#endif /* Spine_BoneData_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BoundingBoxAttachment.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BoundingBoxAttachment.h new file mode 100644 index 0000000..b57d796 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/BoundingBoxAttachment.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_BoundingBoxAttachment_h +#define Spine_BoundingBoxAttachment_h + +#include +#include +#include + +namespace spine { + /// Attachment that has a polygon for bounds checking. + class SP_API BoundingBoxAttachment : public VertexAttachment { + RTTI_DECL + + public: + explicit BoundingBoxAttachment(const String &name); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + Color _color; + }; +} + +#endif /* Spine_BoundingBoxAttachment_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ClippingAttachment.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ClippingAttachment.h new file mode 100644 index 0000000..7969ea8 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ClippingAttachment.h @@ -0,0 +1,65 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ClippingAttachment_h +#define Spine_ClippingAttachment_h + +#include +#include + +namespace spine { + class SlotData; + + class SP_API ClippingAttachment : public VertexAttachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class SkeletonClipping; + + RTTI_DECL + + public: + explicit ClippingAttachment(const String &name); + + SlotData *getEndSlot(); + + void setEndSlot(SlotData *inValue); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + SlotData *_endSlot; + Color _color; + }; +} + +#endif /* Spine_ClippingAttachment_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Color.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Color.h new file mode 100644 index 0000000..9a8cdeb --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Color.h @@ -0,0 +1,110 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_COLOR_H +#define SPINE_COLOR_H + +#include + +namespace spine { + class SP_API Color : public SpineObject { + public: + Color() : r(0), g(0), b(0), a(0) { + } + + Color(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) { + clamp(); + } + + inline Color &set(float _r, float _g, float _b, float _a) { + this->r = _r; + this->g = _g; + this->b = _b; + this->a = _a; + clamp(); + return *this; + } + + inline Color &set(float _r, float _g, float _b) { + this->r = _r; + this->g = _g; + this->b = _b; + clamp(); + return *this; + } + + inline Color &set(const Color &other) { + r = other.r; + g = other.g; + b = other.b; + a = other.a; + clamp(); + return *this; + } + + inline Color &add(float _r, float _g, float _b, float _a) { + this->r += _r; + this->g += _g; + this->b += _b; + this->a += _a; + clamp(); + return *this; + } + + inline Color &add(float _r, float _g, float _b) { + this->r += _r; + this->g += _g; + this->b += _b; + clamp(); + return *this; + } + + inline Color &add(const Color &other) { + r += other.r; + g += other.g; + b += other.b; + a += other.a; + clamp(); + return *this; + } + + inline Color &clamp() { + r = MathUtil::clamp(this->r, 0, 1); + g = MathUtil::clamp(this->g, 0, 1); + b = MathUtil::clamp(this->b, 0, 1); + a = MathUtil::clamp(this->a, 0, 1); + return *this; + } + + float r, g, b, a; + }; +} + + +#endif //SPINE_COLOR_H diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ColorTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ColorTimeline.h new file mode 100644 index 0000000..3760b1b --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ColorTimeline.h @@ -0,0 +1,197 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ColorTimeline_h +#define Spine_ColorTimeline_h + +#include + +namespace spine { + class SP_API RGBATimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGBATimeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGBATimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b, float a); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 5; + static const int R = 1; + static const int G = 2; + static const int B = 3; + static const int A = 4; + }; + + class SP_API RGBTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGBTimeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGBTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 4; + static const int R = 1; + static const int G = 2; + static const int B = 3; + }; + + class SP_API AlphaTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit AlphaTimeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~AlphaTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + }; + + class SP_API RGBA2Timeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGBA2Timeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGBA2Timeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 8; + static const int R = 1; + static const int G = 2; + static const int B = 3; + static const int A = 4; + static const int R2 = 5; + static const int G2 = 6; + static const int B2 = 7; + }; + + class SP_API RGB2Timeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit RGB2Timeline(size_t frameCount, size_t bezierCount, int slotIndex); + + virtual ~RGB2Timeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frame, float time, float r, float g, float b, float r2, float g2, float b2); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + static const int ENTRIES = 7; + static const int R = 1; + static const int G = 2; + static const int B = 3; + static const int R2 = 4; + static const int G2 = 5; + static const int B2 = 6; + }; +} + +#endif /* Spine_ColorTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ConstraintData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ConstraintData.h new file mode 100644 index 0000000..ab51f32 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ConstraintData.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Constraint_h +#define Spine_Constraint_h + +#include +#include + +namespace spine { + /// The interface for all constraints. + class SP_API ConstraintData : public SpineObject { + + friend class SkeletonBinary; + + RTTI_DECL + + public: + ConstraintData(const String &name); + + virtual ~ConstraintData(); + + /// The IK constraint's name, which is unique within the skeleton. + const String &getName(); + + /// The ordinal for the order a skeleton's constraints will be applied. + size_t getOrder(); + + void setOrder(size_t inValue); + + /// Whether the constraint is only active for a specific skin. + bool isSkinRequired(); + + void setSkinRequired(bool inValue); + + private: + const String _name; + size_t _order; + bool _skinRequired; + }; +} + +#endif /* Spine_Constraint_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ContainerUtil.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ContainerUtil.h new file mode 100644 index 0000000..410e2eb --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ContainerUtil.h @@ -0,0 +1,129 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ContainerUtil_h +#define Spine_ContainerUtil_h + +#include +#include +#include +#include +#include + +#include + +namespace spine { + class SP_API ContainerUtil : public SpineObject { + public: + /// Finds an item by comparing each item's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + template + static T *findWithName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0; i < items.size(); ++i) { + T *item = items[i]; + if (item->getName() == name) { + return item; + } + } + + return NULL; + } + + /// @return -1 if the item was not found. + template + static int findIndexWithName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0, len = items.size(); i < len; ++i) { + T *item = items[i]; + if (item->getName() == name) { + return static_cast(i); + } + } + + return -1; + } + + /// Finds an item by comparing each item's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + template + static T *findWithDataName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0; i < items.size(); ++i) { + T *item = items[i]; + if (item->getData().getName() == name) { + return item; + } + } + + return NULL; + } + + /// @return -1 if the item was not found. + template + static int findIndexWithDataName(Vector &items, const String &name) { + assert(name.length() > 0); + + for (size_t i = 0, len = items.size(); i < len; ++i) { + T *item = items[i]; + if (item->getData().getName() == name) { + return static_cast(i); + } + } + + return -1; + } + + template + static void cleanUpVectorOfPointers(Vector &items) { + for (int i = (int) items.size() - 1; i >= 0; i--) { + T *item = items[i]; + + delete item; + + items.removeAt(i); + } + } + + private: + // ctor, copy ctor, and assignment should be private in a Singleton + ContainerUtil(); + + ContainerUtil(const ContainerUtil &); + + ContainerUtil &operator=(const ContainerUtil &); + }; +} + +#endif /* Spine_ContainerUtil_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/CurveTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/CurveTimeline.h new file mode 100644 index 0000000..7eaac0e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/CurveTimeline.h @@ -0,0 +1,111 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_CurveTimeline_h +#define Spine_CurveTimeline_h + +#include +#include + +namespace spine { + /// Base class for frames that use an interpolation bezier curve. + class SP_API CurveTimeline : public Timeline { + RTTI_DECL + + public: + explicit CurveTimeline(size_t frameCount, size_t frameEntries, size_t bezierCount); + + virtual ~CurveTimeline(); + + void setLinear(size_t frame); + + void setStepped(size_t frame); + + virtual void + setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2); + + float getBezierValue(float time, size_t frame, size_t valueOffset, size_t i); + + Vector &getCurves(); + + protected: + static const int LINEAR = 0; + static const int STEPPED = 1; + static const int BEZIER = 2; + static const int BEZIER_SIZE = 18; + + Vector _curves; // type, x, y, ... + }; + + class SP_API CurveTimeline1 : public CurveTimeline { + RTTI_DECL + + public: + explicit CurveTimeline1(size_t frameCount, size_t bezierCount); + + virtual ~CurveTimeline1(); + + void setFrame(size_t frame, float time, float value); + + float getCurveValue(float time); + + float getRelativeValue(float time, float alpha, MixBlend blend, float current, float setup); + + float getAbsoluteValue(float time, float alpha, MixBlend blend, float current, float setup); + + float getAbsoluteValue (float time, float alpha, MixBlend blend, float current, float setup, float value); + + float getScaleValue (float time, float alpha, MixBlend blend, MixDirection direction, float current, float setup); + + protected: + static const int ENTRIES = 2; + static const int VALUE = 1; + }; + + class SP_API CurveTimeline2 : public CurveTimeline { + RTTI_DECL + + public: + explicit CurveTimeline2(size_t frameCount, size_t bezierCount); + + virtual ~CurveTimeline2(); + + void setFrame(size_t frame, float time, float value1, float value2); + + float getCurveValue(float time); + + protected: + static const int ENTRIES = 3; + static const int VALUE1 = 1; + static const int VALUE2 = 2; + }; +} + +#endif /* Spine_CurveTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Debug.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Debug.h new file mode 100644 index 0000000..914afa5 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Debug.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_LOG_H +#define SPINE_LOG_H + +#include +#include + +#include + +namespace spine { + + class SP_API DebugExtension : public SpineExtension { + struct Allocation { + void *address; + size_t size; + const char *fileName; + int line; + + Allocation() : address(NULL), size(0), fileName(NULL), line(0) { + } + + Allocation(void *a, size_t s, const char *f, int l) : address(a), size(s), fileName(f), line(l) { + } + }; + + public: + DebugExtension(SpineExtension *extension) : _extension(extension), _allocations(0), _reallocations(0), + _frees(0) { + } + + void reportLeaks() { + for (std::map::iterator it = _allocated.begin(); it != _allocated.end(); it++) { + printf("\"%s:%i (%zu bytes at %p)\n", it->second.fileName, it->second.line, it->second.size, + it->second.address); + } + printf("allocations: %zu, reallocations: %zu, frees: %zu\n", _allocations, _reallocations, _frees); + if (_allocated.empty()) printf("No leaks detected\n"); + } + + void clearAllocations() { + _allocated.clear(); + _usedMemory = 0; + } + + virtual void *_alloc(size_t size, const char *file, int line) { + void *result = _extension->_alloc(size, file, line); + _allocated[result] = Allocation(result, size, file, line); + _allocations++; + _usedMemory += size; + return result; + } + + virtual void *_calloc(size_t size, const char *file, int line) { + void *result = _extension->_calloc(size, file, line); + _allocated[result] = Allocation(result, size, file, line); + _allocations++; + _usedMemory += size; + return result; + } + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) { + if (_allocated.count(ptr)) _usedMemory -= _allocated[ptr].size; + _allocated.erase(ptr); + void *result = _extension->_realloc(ptr, size, file, line); + _reallocations++; + _allocated[result] = Allocation(result, size, file, line); + _usedMemory += size; + return result; + } + + virtual void _free(void *mem, const char *file, int line) { + if (_allocated.count(mem)) { + _extension->_free(mem, file, line); + _frees++; + _usedMemory -= _allocated[mem].size; + _allocated.erase(mem); + return; + } + + printf("%s:%i (address %p): Double free or not allocated through SpineExtension\n", file, line, mem); + _extension->_free(mem, file, line); + } + + virtual char *_readFile(const String &path, int *length) { + auto data = _extension->_readFile(path, length); + + if (_allocated.count(data) == 0) { + _allocated[data] = Allocation(data, sizeof(char) * (*length), nullptr, 0); + _allocations++; + _usedMemory += sizeof(char) * (*length); + } + + return data; + } + + size_t getUsedMemory() { + return _usedMemory; + } + + private: + SpineExtension *_extension; + std::map _allocated; + size_t _allocations; + size_t _reallocations; + size_t _frees; + size_t _usedMemory; + }; +} + + +#endif //SPINE_LOG_H diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/DeformTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/DeformTimeline.h new file mode 100644 index 0000000..4209d20 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/DeformTimeline.h @@ -0,0 +1,80 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_DeformTimeline_h +#define Spine_DeformTimeline_h + +#include + +namespace spine { + class VertexAttachment; + + class SP_API DeformTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit DeformTimeline(size_t frameCount, size_t bezierCount, int slotIndex, VertexAttachment *attachment); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(int frameIndex, float time, Vector &vertices); + + Vector > &getVertices(); + + VertexAttachment *getAttachment(); + + void setAttachment(VertexAttachment *inValue); + + virtual void + setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2); + + float getCurvePercent(float time, int frame); + + int getSlotIndex() { return _slotIndex; } + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + protected: + int _slotIndex; + + Vector > _vertices; + + VertexAttachment *_attachment; + }; +} + +#endif /* Spine_DeformTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/DrawOrderTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/DrawOrderTimeline.h new file mode 100644 index 0000000..7d2ab55 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/DrawOrderTimeline.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_DrawOrderTimeline_h +#define Spine_DrawOrderTimeline_h + +#include + +namespace spine { + class SP_API DrawOrderTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit DrawOrderTimeline(size_t frameCount); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + /// @param drawOrder May be NULL to use bind pose draw order + void setFrame(size_t frame, float time, Vector &drawOrder); + + Vector > &getDrawOrders(); + + private: + Vector > _drawOrders; + }; +} + +#endif /* Spine_DrawOrderTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Event.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Event.h new file mode 100644 index 0000000..b838a48 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Event.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Event_h +#define Spine_Event_h + +#include +#include + +namespace spine { + class EventData; + +/// Stores the current pose values for an Event. + class SP_API Event : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + public: + Event(float time, const EventData &data); + + const EventData &getData(); + + /// The animation time this event was keyed. + float getTime(); + + int getIntValue(); + + void setIntValue(int inValue); + + float getFloatValue(); + + void setFloatValue(float inValue); + + const String &getStringValue(); + + void setStringValue(const String &inValue); + + float getVolume(); + + void setVolume(float inValue); + + float getBalance(); + + void setBalance(float inValue); + + private: + const EventData &_data; + const float _time; + int _intValue; + float _floatValue; + String _stringValue; + float _volume; + float _balance; + }; +} + +#endif /* Spine_Event_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/EventData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/EventData.h new file mode 100644 index 0000000..fa35160 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/EventData.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_EventData_h +#define Spine_EventData_h + +#include +#include + +namespace spine { +/// Stores the setup pose values for an Event. + class SP_API EventData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Event; + + public: + explicit EventData(const String &name); + + /// The name of the event, which is unique within the skeleton. + const String &getName() const; + + int getIntValue() const; + + void setIntValue(int inValue); + + float getFloatValue() const; + + void setFloatValue(float inValue); + + const String &getStringValue() const; + + void setStringValue(const String &inValue); + + const String &getAudioPath() const; + + void setAudioPath(const String &inValue); + + float getVolume() const; + + void setVolume(float inValue); + + float getBalance() const; + + void setBalance(float inValue); + + private: + const String _name; + int _intValue; + float _floatValue; + String _stringValue; + String _audioPath; + float _volume; + float _balance; + }; +} + +#endif /* Spine_EventData_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/EventTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/EventTimeline.h new file mode 100644 index 0000000..bd05a49 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/EventTimeline.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_EventTimeline_h +#define Spine_EventTimeline_h + +#include + +namespace spine { + class SP_API EventTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit EventTimeline(size_t frameCount); + + ~EventTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and value of the specified keyframe. + void setFrame(size_t frame, Event *event); + + Vector &getEvents(); + + private: + Vector _events; + }; +} + +#endif /* Spine_EventTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Extension.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Extension.h new file mode 100644 index 0000000..babb397 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Extension.h @@ -0,0 +1,125 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Extension_h +#define Spine_Extension_h + + +#include +#include + +#define SP_UNUSED(x) (void)(x) + +namespace spine { + class String; + + class SP_API SpineExtension { + public: + template + static T *alloc(size_t num, const char *file, int line) { + return (T *) getInstance()->_alloc(sizeof(T) * num, file, line); + } + + template + static T *calloc(size_t num, const char *file, int line) { + return (T *) getInstance()->_calloc(sizeof(T) * num, file, line); + } + + template + static T *realloc(T *ptr, size_t num, const char *file, int line) { + return (T *) getInstance()->_realloc(ptr, sizeof(T) * num, file, line); + } + + template + static void free(T *ptr, const char *file, int line) { + getInstance()->_free((void *) ptr, file, line); + } + + template + static void beforeFree(T *ptr) { + getInstance()->_beforeFree((void *) ptr); + } + + static char *readFile(const String &path, int *length) { + return getInstance()->_readFile(path, length); + } + + static void setInstance(SpineExtension *inSpineExtension); + + static SpineExtension *getInstance(); + + virtual ~SpineExtension(); + + /// Implement this function to use your own memory allocator + virtual void *_alloc(size_t size, const char *file, int line) = 0; + + virtual void *_calloc(size_t size, const char *file, int line) = 0; + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) = 0; + + /// If you provide a spineAllocFunc, you should also provide a spineFreeFunc + virtual void _free(void *mem, const char *file, int line) = 0; + + virtual char *_readFile(const String &path, int *length) = 0; + + virtual void _beforeFree(void *ptr) { SP_UNUSED(ptr); } + + protected: + SpineExtension(); + + private: + static SpineExtension *_instance; + }; + + class SP_API DefaultSpineExtension : public SpineExtension { + public: + DefaultSpineExtension(); + + virtual ~DefaultSpineExtension(); + + protected: + virtual void *_alloc(size_t size, const char *file, int line) override; + + virtual void *_calloc(size_t size, const char *file, int line) override; + + virtual void *_realloc(void *ptr, size_t size, const char *file, int line) override; + + virtual void _free(void *mem, const char *file, int line) override; + + virtual char *_readFile(const String &path, int *length) override; + }; + +// This function is to be implemented by engine specific runtimes to provide +// the default extension for that engine. It is called the first time +// SpineExtension::getInstance() is called, when no instance has been set +// yet. + extern SpineExtension *getDefaultExtension(); +} + +#endif /* Spine_Extension_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/HasRendererObject.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/HasRendererObject.h new file mode 100644 index 0000000..db49a48 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/HasRendererObject.h @@ -0,0 +1,65 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_HasRendererObject_h +#define Spine_HasRendererObject_h + +#include + +namespace spine { + + typedef void (*DisposeRendererObject)(void *rendererObject); + + class SP_API HasRendererObject { + public: + explicit HasRendererObject() : _rendererObject(0), _dispose(0) {}; + + virtual ~HasRendererObject() { + if (_dispose && _rendererObject) + _dispose(_rendererObject); + } + + void *getRendererObject() { return _rendererObject; } + + void setRendererObject(void *rendererObject, DisposeRendererObject dispose = 0) { + if (_dispose && _rendererObject && _rendererObject != rendererObject) + _dispose(_rendererObject); + + _rendererObject = rendererObject; + _dispose = dispose; + } + + private: + void *_rendererObject; + DisposeRendererObject _dispose; + }; + +} + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/HashMap.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/HashMap.h new file mode 100644 index 0000000..45c54b8 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/HashMap.h @@ -0,0 +1,200 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_HashMap_h +#define Spine_HashMap_h + +#include +#include + +// Required for new with line number and file name in MSVC +#ifdef _MSC_VER +#pragma warning(disable:4291) + +#pragma warning(disable:4251) + +#endif + +namespace spine { + template + class SP_API HashMap : public SpineObject { + private: + class Entry; + + public: + class SP_API Pair { + public: + explicit Pair(K &k, V &v) : key(k), value(v) {} + + K &key; + V &value; + }; + + class SP_API Entries { + public: + friend class HashMap; + + explicit Entries(Entry *entry) : _entry(NULL), _hasChecked(false) { + _start.next = entry; + _entry = &_start; + } + + Pair next() { + assert(_entry); + assert(_hasChecked); + _entry = _entry->next; + Pair pair(_entry->_key, _entry->_value); + _hasChecked = false; + return pair; + } + + bool hasNext() { + _hasChecked = true; + return _entry->next; + } + + private: + bool _hasChecked; + Entry _start; + Entry *_entry; + }; + + HashMap() : + _head(NULL), + _size(0) { + } + + ~HashMap() { + clear(); + } + + void clear() { + for (Entry *entry = _head; entry != NULL;) { + Entry *next = entry->next; + delete entry; + entry = next; + } + _head = NULL; + _size = 0; + } + + size_t size() { + return _size; + } + + void put(const K &key, const V &value) { + Entry *entry = find(key); + if (entry) { + entry->_key = key; + entry->_value = value; + } else { + entry = new(__FILE__, __LINE__) Entry(); + entry->_key = key; + entry->_value = value; + + Entry *oldHead = _head; + + if (oldHead) { + _head = entry; + oldHead->prev = entry; + entry->next = oldHead; + } else { + _head = entry; + } + _size++; + } + } + + bool addAll(Vector &keys, const V &value) { + size_t oldSize = _size; + for (size_t i = 0; i < keys.size(); i++) { + put(keys[i], value); + } + return _size != oldSize; + } + + bool containsKey(const K &key) { + return find(key) != NULL; + } + + bool remove(const K &key) { + Entry *entry = find(key); + if (!entry) return false; + + Entry *prev = entry->prev; + Entry *next = entry->next; + + if (prev) prev->next = next; + else _head = next; + if (next) next->prev = entry->prev; + + delete entry; + _size--; + + return true; + } + + V operator[](const K &key) { + Entry *entry = find(key); + if (entry) return entry->_value; + else { + assert(false); + return 0; + } + } + + Entries getEntries() const { + return Entries(_head); + } + + private: + Entry *find(const K &key) { + for (Entry *entry = _head; entry != NULL; entry = entry->next) { + if (entry->_key == key) + return entry; + } + return NULL; + } + + class SP_API Entry : public SpineObject { + public: + K _key; + V _value; + Entry *next; + Entry *prev; + + Entry() : next(NULL), prev(NULL) {} + }; + + Entry *_head; + size_t _size; + }; +} + +#endif /* Spine_HashMap_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/IkConstraint.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/IkConstraint.h new file mode 100644 index 0000000..077f4f0 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/IkConstraint.h @@ -0,0 +1,118 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_IkConstraint_h +#define Spine_IkConstraint_h + +#include + +#include + +namespace spine { + class IkConstraintData; + + class Skeleton; + + class Bone; + + class SP_API IkConstraint : public Updatable { + friend class Skeleton; + + friend class IkConstraintTimeline; + + RTTI_DECL + + public: + /// Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified + /// in the world coordinate system. + static void + apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha); + + /// Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as + /// possible. The target is specified in the world coordinate system. + /// @param child A direct descendant of the parent bone. + static void + apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, + float alpha); + + IkConstraint(IkConstraintData &data, Skeleton &skeleton); + + virtual void update(Physics physics); + + virtual int getOrder(); + + IkConstraintData &getData(); + + Vector &getBones(); + + Bone *getTarget(); + + void setTarget(Bone *inValue); + + int getBendDirection(); + + void setBendDirection(int inValue); + + bool getCompress(); + + void setCompress(bool inValue); + + bool getStretch(); + + void setStretch(bool inValue); + + float getMix(); + + void setMix(float inValue); + + float getSoftness(); + + void setSoftness(float inValue); + + bool isActive(); + + void setActive(bool inValue); + + void setToSetupPose(); + + private: + IkConstraintData &_data; + Vector _bones; + int _bendDirection; + bool _compress; + bool _stretch; + float _mix; + float _softness; + Bone *_target; + bool _active; + }; +} + +#endif /* Spine_IkConstraint_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/IkConstraintData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/IkConstraintData.h new file mode 100644 index 0000000..2d953d4 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/IkConstraintData.h @@ -0,0 +1,102 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_IkConstraintData_h +#define Spine_IkConstraintData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API IkConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class IkConstraint; + + friend class Skeleton; + + friend class IkConstraintTimeline; + + public: + RTTI_DECL + + explicit IkConstraintData(const String &name); + + /// The bones that are constrained by this IK Constraint. + Vector &getBones(); + + /// The bone that is the IK target. + BoneData *getTarget(); + + void setTarget(BoneData *inValue); + + /// Controls the bend direction of the IK bones, either 1 or -1. + int getBendDirection(); + + void setBendDirection(int inValue); + + bool getCompress(); + + void setCompress(bool inValue); + + bool getStretch(); + + void setStretch(bool inValue); + + bool getUniform(); + + void setUniform(bool inValue); + + float getMix(); + + void setMix(float inValue); + + float getSoftness(); + + void setSoftness(float inValue); + + private: + Vector _bones; + BoneData *_target; + int _bendDirection; + bool _compress; + bool _stretch; + bool _uniform; + float _mix; + float _softness; + }; +} + +#endif /* Spine_IkConstraintData_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/IkConstraintTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/IkConstraintTimeline.h new file mode 100644 index 0000000..11dcf2e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/IkConstraintTimeline.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_IkConstraintTimeline_h +#define Spine_IkConstraintTimeline_h + +#include + +namespace spine { + + class SP_API IkConstraintTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit IkConstraintTimeline(size_t frameCount, size_t bezierCount, int ikConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time, mix and bend direction of the specified keyframe. + void setFrame(int frame, float time, float mix, float softness, int bendDirection, bool compress, bool stretch); + + int getIkConstraintIndex() { return _constraintIndex; } + + void setIkConstraintIndex(int inValue) { _constraintIndex = inValue; } + + private: + int _constraintIndex; + + static const int ENTRIES = 6; + static const int MIX = 1; + static const int SOFTNESS = 2; + static const int BEND_DIRECTION = 3; + static const int COMPRESS = 4; + static const int STRETCH = 5; + }; +} + +#endif /* Spine_IkConstraintTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Inherit.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Inherit.h new file mode 100644 index 0000000..fcc3a74 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Inherit.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformMode_h +#define Spine_TransformMode_h + +namespace spine { + enum Inherit { + Inherit_Normal = 0, + Inherit_OnlyTranslation, + Inherit_NoRotationOrReflection, + Inherit_NoScale, + Inherit_NoScaleOrReflection + }; +} + +#endif /* Spine_TransformMode_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/InheritTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/InheritTimeline.h new file mode 100644 index 0000000..6c79eed --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/InheritTimeline.h @@ -0,0 +1,71 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_InheritTimeline_h +#define Spine_InheritTimeline_h + +#include + +#include +#include +#include + +namespace spine { + + class SP_API InheritTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit InheritTimeline(size_t frameCount, int boneIndex); + + virtual ~InheritTimeline(); + + void setFrame(int frame, float time, Inherit inherit); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + + static const int ENTRIES = 2; + static const int INHERIT = 1; + }; +} + +#endif /* Spine_InheritTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Json.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Json.h new file mode 100644 index 0000000..03809a1 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Json.h @@ -0,0 +1,116 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Json_h +#define Spine_Json_h + +#include + +#ifndef SPINE_JSON_HAVE_PREV +/* spine doesn't use the "prev" link in the Json sibling lists. */ +#define SPINE_JSON_HAVE_PREV 0 +#endif + +namespace spine { + class SP_API Json : public SpineObject { + friend class SkeletonJson; + + public: + /* Json Types: */ + static const int JSON_FALSE; + static const int JSON_TRUE; + static const int JSON_NULL; + static const int JSON_NUMBER; + static const int JSON_STRING; + static const int JSON_ARRAY; + static const int JSON_OBJECT; + + /* Get item "string" from object. Case insensitive. */ + static Json *getItem(Json *object, const char *string); + + static Json *getItem(Json *object, int childIndex); + + static const char *getString(Json *object, const char *name, const char *defaultValue); + + static float getFloat(Json *object, const char *name, float defaultValue); + + static int getInt(Json *object, const char *name, int defaultValue); + + static bool getBoolean(Json *object, const char *name, bool defaultValue); + + /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when Json_create() returns 0. 0 when Json_create() succeeds. */ + static const char *getError(); + + /* Supply a block of JSON, and this returns a Json object you can interrogate. Call Json_dispose when finished. */ + explicit Json(const char *value); + + ~Json(); + + + private: + static const char *_error; + + Json *_next; +#if SPINE_JSON_HAVE_PREV + Json* _prev; /* next/prev allow you to walk array/object chains. Alternatively, use getSize/getItem */ +#endif + Json *_child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + + int _type; /* The type of the item, as above. */ + int _size; /* The number of children. */ + + const char *_valueString; /* The item's string, if type==JSON_STRING */ + int _valueInt; /* The item's number, if type==JSON_NUMBER */ + float _valueFloat; /* The item's number, if type==JSON_NUMBER */ + + const char *_name; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + + /* Utility to jump whitespace and cr/lf */ + static const char *skip(const char *inValue); + + /* Parser core - when encountering text, process appropriately. */ + static const char *parseValue(Json *item, const char *value); + + /* Parse the input text into an unescaped cstring, and populate item. */ + static const char *parseString(Json *item, const char *str); + + /* Parse the input text to generate a number, and populate the result into item. */ + static const char *parseNumber(Json *item, const char *num); + + /* Build an array from input text. */ + static const char *parseArray(Json *item, const char *value); + + /* Build an object from the text. */ + static const char *parseObject(Json *item, const char *value); + + static int json_strcasecmp(const char *s1, const char *s2); + }; +} + +#endif /* Spine_Json_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/LinkedMesh.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/LinkedMesh.h new file mode 100644 index 0000000..bd71970 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/LinkedMesh.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_LinkedMesh_h +#define Spine_LinkedMesh_h + +#include +#include + +namespace spine { + class MeshAttachment; + + class SP_API LinkedMesh : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + public: + LinkedMesh(MeshAttachment *mesh, const int skinIndex, size_t slotIndex, const String &parent, + bool inheritTimeline); + + LinkedMesh(MeshAttachment *mesh, const String &skin, size_t slotIndex, const String &parent, + bool inheritTimeline); + + private: + MeshAttachment *_mesh; + int _skinIndex; + String _skin; + size_t _slotIndex; + String _parent; + bool _inheritTimeline; + }; +} + +#endif /* Spine_LinkedMesh_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Log.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Log.h new file mode 100644 index 0000000..ce66f44 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Log.h @@ -0,0 +1,57 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_DEBUG_LOG_H +#define SPINE_DEBUG_LOG_H + +#include + +namespace spine { + SP_API void spDebug_printSkeletonData(SkeletonData *skeletonData); + + SP_API void spDebug_printAnimation(Animation *animation); + + SP_API void spDebug_printTimeline(Timeline *timeline); + + SP_API void spDebug_printBoneDatas(Vector &boneDatas); + + SP_API void spDebug_printBoneData(BoneData *boneData); + + SP_API void spDebug_printSkeleton(Skeleton *skeleton); + + SP_API void spDebug_printBones(Vector &bones); + + SP_API void spDebug_printBone(Bone *bone); + + SP_API void spDebug_printFloats(float *values, int numFloats); + + SP_API void spDebug_printFloats(Vector &values); +} + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MathUtil.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MathUtil.h new file mode 100644 index 0000000..fb6d24b --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MathUtil.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MathUtil_h +#define Spine_MathUtil_h + +#include + +#include +// Needed for older MSVC versions +#undef min +#undef max + +namespace spine { + + class SP_API MathUtil : public SpineObject { + private: + MathUtil(); + + public: + static const float Pi; + static const float Pi_2; + static const float InvPi_2; + static const float Deg_Rad; + static const float Rad_Deg; + + template + static inline T min(T a, T b) { return a < b ? a : b; } + + template + static inline T max(T a, T b) { return a > b ? a : b; } + + static float sign(float val); + + static float clamp(float x, float lower, float upper); + + static float abs(float v); + + /// Returns the sine in radians from a lookup table. + static float sin(float radians); + + /// Returns the cosine in radians from a lookup table. + static float cos(float radians); + + /// Returns the sine in radians from a lookup table. + static float sinDeg(float degrees); + + /// Returns the cosine in radians from a lookup table. + static float cosDeg(float degrees); + + /// Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323 + /// degrees), largest error of 0.00488 radians (0.2796 degrees). + static float atan2(float y, float x); + + static float atan2Deg(float x, float y); + + static float acos(float v); + + static float sqrt(float v); + + static float fmod(float a, float b); + + static bool isNan(float v); + + static float quietNan(); + + static float random(); + + static float randomTriangular(float min, float max); + + static float randomTriangular(float min, float max, float mode); + + static float pow(float a, float b); + + static float ceil(float v); + }; + + struct SP_API Interpolation { + virtual float apply(float a) = 0; + + virtual float interpolate(float start, float end, float a) { + return start + (end - start) * apply(a); + } + + virtual ~Interpolation() {}; + }; + + struct SP_API PowInterpolation : public Interpolation { + PowInterpolation(int power) : power(power) { + } + + float apply(float a) { + if (a <= 0.5f) return MathUtil::pow(a * 2.0f, (float) power) / 2.0f; + return MathUtil::pow((a - 1.0f) * 2.0f, (float) power) / (power % 2 == 0 ? -2.0f : 2.0f) + 1.0f; + } + + int power; + }; + + struct SP_API PowOutInterpolation : public Interpolation { + PowOutInterpolation(int power) : power(power) { + } + + float apply(float a) { + return MathUtil::pow(a - 1, (float) power) * (power % 2 == 0 ? -1.0f : 1.0f) + 1.0f; + } + + int power; + }; + +} + +#endif /* Spine_MathUtil_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MeshAttachment.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MeshAttachment.h new file mode 100644 index 0000000..a553766 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MeshAttachment.h @@ -0,0 +1,122 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MeshAttachment_h +#define Spine_MeshAttachment_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + /// Attachment that displays a texture region using a mesh. + class SP_API MeshAttachment : public VertexAttachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AtlasAttachmentLoader; + + RTTI_DECL + + public: + explicit MeshAttachment(const String &name); + + virtual ~MeshAttachment(); + + using VertexAttachment::computeWorldVertices; + + virtual void computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride = 2); + + void updateRegion(); + + int getHullLength(); + + void setHullLength(int inValue); + + Vector &getRegionUVs(); + + /// The UV pair for each vertex, normalized within the entire texture. See also MeshAttachment::updateRegion + Vector &getUVs(); + + Vector &getTriangles(); + + Color &getColor(); + + const String &getPath(); + + void setPath(const String &inValue); + + TextureRegion *getRegion(); + + void setRegion(TextureRegion *region); + + Sequence *getSequence(); + + void setSequence(Sequence *sequence); + + MeshAttachment *getParentMesh(); + + void setParentMesh(MeshAttachment *inValue); + + // Nonessential. + Vector &getEdges(); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + virtual Attachment *copy(); + + MeshAttachment *newLinkedMesh(); + + private: + MeshAttachment *_parentMesh; + Vector _uvs; + Vector _regionUVs; + Vector _triangles; + Vector _edges; + String _path; + Color _color; + int _hullLength; + int _width, _height; + TextureRegion *_region; + Sequence *_sequence; + }; +} + +#endif /* Spine_MeshAttachment_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MixBlend.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MixBlend.h new file mode 100644 index 0000000..3aa0faf --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MixBlend.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MixPose_h +#define Spine_MixPose_h + +namespace spine { + +/// Controls how a timeline is mixed with the setup or current pose. +/// See also Timeline::apply(Skeleton&, float, float, Vector&, float, Blend, MixDirection) + enum MixBlend { + MixBlend_Setup = 0, + MixBlend_First, + MixBlend_Replace, + MixBlend_Add + }; +} + +#endif /* Spine_MixPose_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MixDirection.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MixDirection.h new file mode 100644 index 0000000..dbb1287 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/MixDirection.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_MixDirection_h +#define Spine_MixDirection_h + +namespace spine { + +/// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose) or mixing in toward 1 (the timeline's pose). +/// See also Timeline::apply(Skeleton&, float, float, Vector&, float, MixPose, MixDirection) + enum MixDirection { + MixDirection_In = 0, + MixDirection_Out + }; + +} + +#endif /* Spine_MixDirection_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathAttachment.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathAttachment.h new file mode 100644 index 0000000..e137849 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathAttachment.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathAttachment_h +#define Spine_PathAttachment_h + +#include +#include + +namespace spine { + class SP_API PathAttachment : public VertexAttachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PathAttachment(const String &name); + + /// The length in the setup pose from the start of the path to the end of each curve. + Vector &getLengths(); + + bool isClosed(); + + void setClosed(bool inValue); + + bool isConstantSpeed(); + + void setConstantSpeed(bool inValue); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + Vector _lengths; + bool _closed; + bool _constantSpeed; + Color _color; + }; +} + +#endif /* Spine_PathAttachment_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraint.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraint.h new file mode 100644 index 0000000..924b354 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraint.h @@ -0,0 +1,133 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraint_h +#define Spine_PathConstraint_h + +#include + +#include + +namespace spine { + class PathConstraintData; + + class Skeleton; + + class PathAttachment; + + class Bone; + + class Slot; + + class SP_API PathConstraint : public Updatable { + friend class Skeleton; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + RTTI_DECL + + public: + PathConstraint(PathConstraintData &data, Skeleton &skeleton); + + virtual void update(Physics physics); + + virtual int getOrder(); + + PathConstraintData &getData(); + + Vector &getBones(); + + Slot *getTarget(); + + void setTarget(Slot *inValue); + + float getPosition(); + + void setPosition(float inValue); + + float getSpacing(); + + void setSpacing(float inValue); + + float getMixRotate(); + + void setMixRotate(float inValue); + + float getMixX(); + + void setMixX(float inValue); + + float getMixY(); + + void setMixY(float inValue); + + bool isActive(); + + void setActive(bool inValue); + + void setToSetupPose(); + + private: + static const float EPSILON; + static const int NONE; + static const int BEFORE; + static const int AFTER; + + PathConstraintData &_data; + Vector _bones; + Slot *_target; + float _position, _spacing; + float _mixRotate, _mixX, _mixY; + + Vector _spaces; + Vector _positions; + Vector _world; + Vector _curves; + Vector _lengths; + Vector _segments; + + bool _active; + + Vector &computeWorldPositions(PathAttachment &path, int spacesCount, bool tangents); + + static void addBeforePosition(float p, Vector &temp, int i, Vector &output, int o); + + static void addAfterPosition(float p, Vector &temp, int i, Vector &output, int o); + + static void + addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + Vector &output, int o, bool tangents); + }; +} + +#endif /* Spine_PathConstraint_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintData.h new file mode 100644 index 0000000..b018ad3 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintData.h @@ -0,0 +1,119 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintData_h +#define Spine_PathConstraintData_h + +#include +#include +#include +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SlotData; + + class SP_API PathConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class PathConstraint; + + friend class Skeleton; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + public: + RTTI_DECL + + explicit PathConstraintData(const String &name); + + Vector &getBones(); + + SlotData *getTarget(); + + void setTarget(SlotData *inValue); + + PositionMode getPositionMode(); + + void setPositionMode(PositionMode inValue); + + SpacingMode getSpacingMode(); + + void setSpacingMode(SpacingMode inValue); + + RotateMode getRotateMode(); + + void setRotateMode(RotateMode inValue); + + float getOffsetRotation(); + + void setOffsetRotation(float inValue); + + float getPosition(); + + void setPosition(float inValue); + + float getSpacing(); + + void setSpacing(float inValue); + + float getMixRotate(); + + void setMixRotate(float inValue); + + float getMixX(); + + void setMixX(float inValue); + + float getMixY(); + + void setMixY(float inValue); + + private: + Vector _bones; + SlotData *_target; + PositionMode _positionMode; + SpacingMode _spacingMode; + RotateMode _rotateMode; + float _offsetRotation; + float _position, _spacing; + float _mixRotate, _mixX, _mixY; + }; +} + +#endif /* Spine_PathConstraintData_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintMixTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintMixTimeline.h new file mode 100644 index 0000000..72c189b --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintMixTimeline.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintMixTimeline_h +#define Spine_PathConstraintMixTimeline_h + +#include + +namespace spine { + + class SP_API PathConstraintMixTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PathConstraintMixTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + /// Sets the time and mixes of the specified keyframe. + void setFrame(int frameIndex, float time, float mixRotate, float mixX, float mixY); + + int getPathConstraintIndex() { return _constraintIndex; } + + void setPathConstraintIndex(int inValue) { _constraintIndex = inValue; } + + private: + int _constraintIndex; + + static const int ENTRIES = 4; + static const int ROTATE = 1; + static const int X = 2; + static const int Y = 3; + }; +} + +#endif /* Spine_PathConstraintMixTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintPositionTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintPositionTimeline.h new file mode 100644 index 0000000..73e4842 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintPositionTimeline.h @@ -0,0 +1,64 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintPositionTimeline_h +#define Spine_PathConstraintPositionTimeline_h + +#include + +namespace spine { + + class SP_API PathConstraintPositionTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + static const int ENTRIES; + + explicit PathConstraintPositionTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex); + + virtual ~PathConstraintPositionTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getPathConstraintIndex() { return _constraintIndex; } + + void setPathConstraintIndex(int inValue) { _constraintIndex = inValue; } + + protected: + int _constraintIndex; + }; +} + +#endif /* Spine_PathConstraintPositionTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintSpacingTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintSpacingTimeline.h new file mode 100644 index 0000000..6d7bee3 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PathConstraintSpacingTimeline.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PathConstraintSpacingTimeline_h +#define Spine_PathConstraintSpacingTimeline_h + +#include + +namespace spine { + class SP_API PathConstraintSpacingTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PathConstraintSpacingTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getPathConstraintIndex() { return _pathConstraintIndex; } + + void setPathConstraintIndex(int inValue) { _pathConstraintIndex = inValue; } + + protected: + int _pathConstraintIndex; + }; +} + +#endif /* Spine_PathConstraintSpacingTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Physics.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Physics.h new file mode 100644 index 0000000..e7b9f7a --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Physics.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Physics_h +#define Spine_Physics_h + +/** Determines how physics and other non-deterministic updates are applied. */ +namespace spine { + enum Physics { + /** Physics are not updated or applied. */ + Physics_None, + + /** Physics are reset to the current pose. */ + Physics_Reset, + + /** Physics are updated and the pose from physics is applied. */ + Physics_Update, + + /** Physics are not updated but the pose from physics is applied. */ + Physics_Pose + }; +} + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PhysicsConstraint.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PhysicsConstraint.h new file mode 100644 index 0000000..b63060e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PhysicsConstraint.h @@ -0,0 +1,197 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PhysicsConstraint_h +#define Spine_PhysicsConstraint_h + +#include + +#include + +namespace spine { + class PhysicsConstraintData; + + class Skeleton; + + class Bone; + + class SP_API PhysicsConstraint : public Updatable { + + friend class Skeleton; + + friend class PhysicsConstraintTimeline; + + friend class PhysicsConstraintInertiaTimeline; + + friend class PhysicsConstraintStrengthTimeline; + + friend class PhysicsConstraintDampingTimeline; + + friend class PhysicsConstraintMassTimeline; + + friend class PhysicsConstraintWindTimeline; + + friend class PhysicsConstraintGravityTimeline; + + friend class PhysicsConstraintMixTimeline; + + friend class PhysicsConstraintResetTimeline; + + RTTI_DECL + + public: + PhysicsConstraint(PhysicsConstraintData& data, Skeleton& skeleton); + + PhysicsConstraintData &getData(); + + void setBone(Bone* bone); + Bone* getBone(); + + void setInertia(float value); + float getInertia(); + + void setStrength(float value); + float getStrength(); + + void setDamping(float value); + float getDamping(); + + void setMassInverse(float value); + float getMassInverse(); + + void setWind(float value); + float getWind(); + + void setGravity(float value); + float getGravity(); + + void setMix(float value); + float getMix(); + + void setReset(bool value); + bool getReset(); + + void setUx(float value); + float getUx(); + + void setUy(float value); + float getUy(); + + void setCx(float value); + float getCx(); + + void setCy(float value); + float getCy(); + + void setTx(float value); + float getTx(); + + void setTy(float value); + float getTy(); + + void setXOffset(float value); + float getXOffset(); + + void setXVelocity(float value); + float getXVelocity(); + + void setYOffset(float value); + float getYOffset(); + + void setYVelocity(float value); + float getYVelocity(); + + void setRotateOffset(float value); + float getRotateOffset(); + + void setRotateVelocity(float value); + float getRotateVelocity(); + + void setScaleOffset(float value); + float getScaleOffset(); + + void setScaleVelocity(float value); + float getScaleVelocity(); + + void setActive(bool value); + bool isActive(); + + void setRemaining(float value); + float getRemaining(); + + void setLastTime(float value); + float getLastTime(); + + void reset(); + + void setToSetupPose(); + + virtual void update(Physics physics); + + void translate(float x, float y); + + void rotate(float x, float y, float degrees); + + private: + PhysicsConstraintData& _data; + Bone* _bone; + + float _inertia; + float _strength; + float _damping; + float _massInverse; + float _wind; + float _gravity; + float _mix; + + bool _reset; + float _ux; + float _uy; + float _cx; + float _cy; + float _tx; + float _ty; + float _xOffset; + float _xVelocity; + float _yOffset; + float _yVelocity; + float _rotateOffset; + float _rotateVelocity; + float _scaleOffset; + float _scaleVelocity; + + bool _active; + + Skeleton& _skeleton; + float _remaining; + float _lastTime; + }; +} + +#endif /* Spine_PhysicsConstraint_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PhysicsConstraintData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PhysicsConstraintData.h new file mode 100644 index 0000000..32aa3cc --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PhysicsConstraintData.h @@ -0,0 +1,151 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PhysicsConstraintData_h +#define Spine_PhysicsConstraintData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API PhysicsConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Skeleton; + + friend class PhysicsConstraint; + + public: + RTTI_DECL + + explicit PhysicsConstraintData(const String &name); + + void setBone(BoneData* bone); + + BoneData* getBone() const; + + void setX(float x); + + float getX() const; + + void setY(float y); + + float getY() const; + + void setRotate(float rotate); + + float getRotate() const; + + void setScaleX(float scaleX); + + float getScaleX() const; + + void setShearX(float shearX); + + float getShearX() const; + + void setLimit(float limit); + + float getLimit() const; + + void setStep(float step); + + float getStep() const; + + void setInertia(float inertia); + + float getInertia() const; + + void setStrength(float strength); + + float getStrength() const; + + void setDamping(float damping); + + float getDamping() const; + + void setMassInverse(float massInverse); + + float getMassInverse() const; + + void setWind(float wind); + + float getWind() const; + + void setGravity(float gravity); + + float getGravity() const; + + void setMix(float mix); + + float getMix() const; + + void setInertiaGlobal(bool inertiaGlobal); + + bool isInertiaGlobal() const; + + void setStrengthGlobal(bool strengthGlobal); + + bool isStrengthGlobal() const; + + void setDampingGlobal(bool dampingGlobal); + + bool isDampingGlobal() const; + + void setMassGlobal(bool massGlobal); + + bool isMassGlobal() const; + + void setWindGlobal(bool windGlobal); + + bool isWindGlobal() const; + + void setGravityGlobal(bool gravityGlobal); + + bool isGravityGlobal() const; + + void setMixGlobal(bool mixGlobal); + + bool isMixGlobal() const; + + private: + BoneData *_bone; + float _x, _y, _rotate, _scaleX, _shearX, _limit; + float _step, _inertia, _strength, _damping, _massInverse, _wind, _gravity, _mix; + bool _inertiaGlobal, _strengthGlobal, _dampingGlobal, _massGlobal, _windGlobal, _gravityGlobal, _mixGlobal; + }; +} + +#endif /* Spine_PhysicsConstraintData_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PhysicsConstraintTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PhysicsConstraintTimeline.h new file mode 100644 index 0000000..e119378 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PhysicsConstraintTimeline.h @@ -0,0 +1,288 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PhysicsConstraintTimeline_h +#define Spine_PhysicsConstraintTimeline_h + +#include +#include +#include + +namespace spine { + + class SP_API PhysicsConstraintTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex, Property property); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getPhysicsConstraintIndex() { return _constraintIndex; } + + void setPhysicsConstraintIndex(int inValue) { _constraintIndex = inValue; } + + protected: + virtual float setup(PhysicsConstraint *constraint) = 0; + virtual float get(PhysicsConstraint *constraint) = 0; + virtual void set(PhysicsConstraint *constraint, float value) = 0; + virtual bool global(PhysicsConstraintData &constraintData) = 0; + + private: + int _constraintIndex; + }; + + class SP_API PhysicsConstraintInertiaTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintInertiaTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintInertia) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getInertia(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_inertia; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_inertia = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isInertiaGlobal(); + } + }; + + class SP_API PhysicsConstraintStrengthTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintStrengthTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintStrength) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getStrength(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_strength; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_strength = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isStrengthGlobal(); + } + }; + + class SP_API PhysicsConstraintDampingTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintDampingTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintDamping) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getDamping(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_damping; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_damping = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isDampingGlobal(); + } + }; + + class SP_API PhysicsConstraintMassTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintMassTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintMass) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return 1 / constraint->_data.getMassInverse(); + } + + float get(PhysicsConstraint *constraint) { + return 1 / constraint->_massInverse; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_massInverse = 1 / value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isMassGlobal(); + } + }; + + class SP_API PhysicsConstraintWindTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintWindTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintWind) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getWind(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_wind; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_wind = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isWindGlobal(); + } + }; + + class SP_API PhysicsConstraintGravityTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintGravityTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintGravity) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getGravity(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_gravity; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_gravity = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isGravityGlobal(); + } + }; + + class SP_API PhysicsConstraintMixTimeline : public PhysicsConstraintTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintMixTimeline(size_t frameCount, size_t bezierCount, int physicsConstraintIndex): PhysicsConstraintTimeline(frameCount, bezierCount, physicsConstraintIndex, Property_PhysicsConstraintMix) {}; + + protected: + float setup(PhysicsConstraint *constraint) { + return constraint->_data.getMix(); + } + + float get(PhysicsConstraint *constraint) { + return constraint->_mix; + } + + void set(PhysicsConstraint *constraint, float value) { + constraint->_mix = value; + } + + bool global(PhysicsConstraintData &constraintData) { + return constraintData.isMixGlobal(); + } + }; + + class SP_API PhysicsConstraintResetTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PhysicsConstraintResetTimeline(size_t frameCount, int physicsConstraintIndex): Timeline(frameCount, 1), _constraintIndex(physicsConstraintIndex) { + PropertyId ids[] = {((PropertyId)Property_PhysicsConstraintReset) << 32}; + setPropertyIds(ids, 1); + } + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + void setFrame(int frame, float time) { + _frames[frame] = time; + } + private: + int _constraintIndex; + }; +} + +#endif /* Spine_PhysicsConstraintTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PointAttachment.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PointAttachment.h new file mode 100644 index 0000000..45f47cf --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PointAttachment.h @@ -0,0 +1,81 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PointAttachment_h +#define Spine_PointAttachment_h + +#include +#include + +namespace spine { + class Bone; + + /// An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be + /// used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a + /// skin. + /// + /// See http://esotericsoftware.com/spine-point-attachments for Point Attachments in the Spine User Guide. + /// + class SP_API PointAttachment : public Attachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit PointAttachment(const String &name); + + void computeWorldPosition(Bone &bone, float &ox, float &oy); + + float computeWorldRotation(Bone &bone); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getRotation(); + + void setRotation(float inValue); + + Color &getColor(); + + virtual Attachment *copy(); + + private: + float _x, _y, _rotation; + Color _color; + }; +} + +#endif /* Spine_PointAttachment_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Pool.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Pool.h new file mode 100644 index 0000000..2f2b43a --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Pool.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Pool_h +#define Spine_Pool_h + +#include +#include +#include +#include + +namespace spine { + template + class SP_API Pool : public SpineObject { + public: + Pool() { + } + + ~Pool() { + ContainerUtil::cleanUpVectorOfPointers(_objects); + } + + T *obtain() { + if (_objects.size() > 0) { + T **object = &_objects[_objects.size() - 1]; + T *ret = *object; + _objects.removeAt(_objects.size() - 1); + + return ret; + } else { + T *ret = new(__FILE__, __LINE__) T(); + + return ret; + } + } + + void free(T *object) { + if (!_objects.contains(object)) { + _objects.add(object); + } + } + + private: + Vector _objects; + }; +} + +#endif /* Spine_Pool_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PositionMode.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PositionMode.h new file mode 100644 index 0000000..ee5b4b0 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/PositionMode.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_PositionMode_h +#define Spine_PositionMode_h + +namespace spine { + enum PositionMode { + PositionMode_Fixed = 0, + PositionMode_Percent + }; +} + +#endif /* Spine_PositionMode_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Property.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Property.h new file mode 100644 index 0000000..69fa24f --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Property.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Property_h +#define Spine_Property_h + +namespace spine { + typedef long long PropertyId; + enum Property { + Property_Rotate = 1 << 0, + Property_X = 1 << 1, + Property_Y = 1 << 2, + Property_ScaleX = 1 << 3, + Property_ScaleY = 1 << 4, + Property_ShearX = 1 << 5, + Property_ShearY = 1 << 6, + Property_Inherit = 1 << 7, + Property_Rgb = 1 << 8, + Property_Alpha = 1 << 9, + Property_Rgb2 = 1 << 10, + Property_Attachment = 1 << 11, + Property_Deform = 1 << 12, + Property_Event = 1 << 13, + Property_DrawOrder = 1 << 14, + Property_IkConstraint = 1 << 15, + Property_TransformConstraint = 1 << 16, + Property_PathConstraintPosition = 1 << 17, + Property_PathConstraintSpacing = 1 << 18, + Property_PathConstraintMix = 1 << 19, + Property_PhysicsConstraintInertia = 1 << 20, + Property_PhysicsConstraintStrength = 1 << 21, + Property_PhysicsConstraintDamping = 1 << 22, + Property_PhysicsConstraintMass = 1 << 23, + Property_PhysicsConstraintWind = 1 << 24, + Property_PhysicsConstraintGravity = 1 << 25, + Property_PhysicsConstraintMix = 1 << 26, + Property_PhysicsConstraintReset = 1 << 27, + Property_Sequence = 1 << 28 + }; +} + +#endif /* Spine_Property_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RTTI.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RTTI.h new file mode 100644 index 0000000..18d6371 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RTTI.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RTTI_h +#define Spine_RTTI_h + +#include + +namespace spine { + class SP_API RTTI { + public: + explicit RTTI(const char *className); + + RTTI(const char *className, const RTTI &baseRTTI); + + const char *getClassName() const; + + bool isExactly(const RTTI &rtti) const; + + bool instanceOf(const RTTI &rtti) const; + + private: + // Prevent copying + RTTI(const RTTI &obj); + + RTTI &operator=(const RTTI &obj); + + const char *_className; + const RTTI *_pBaseRTTI; + }; +} + +#define RTTI_DECL \ +public: \ +static const spine::RTTI rtti; \ +virtual const spine::RTTI& getRTTI() const; + +#define RTTI_IMPL_NOPARENT(name) \ +const spine::RTTI name::rtti(#name); \ +const spine::RTTI& name::getRTTI() const { return rtti; } + +#define RTTI_IMPL(name, parent) \ +const spine::RTTI name::rtti(#name, parent::rtti); \ +const spine::RTTI& name::getRTTI() const { return rtti; } + +#endif /* Spine_RTTI_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RegionAttachment.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RegionAttachment.h new file mode 100644 index 0000000..3d28885 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RegionAttachment.h @@ -0,0 +1,140 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RegionAttachment_h +#define Spine_RegionAttachment_h + +#include +#include +#include +#include +#include + +#include + +#define NUM_UVS 8 + +namespace spine { + class Bone; + + /// Attachment that displays a texture region. + class SP_API RegionAttachment : public Attachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AtlasAttachmentLoader; + + RTTI_DECL + + public: + explicit RegionAttachment(const String &name); + + virtual ~RegionAttachment(); + + void updateRegion(); + + /// Transforms the attachment's four vertices to world coordinates. + /// @param slot The parent slot. + /// @param worldVertices The output world vertices. Must have a length greater than or equal to offset + 8. + /// @param offset The worldVertices index to begin writing values. + /// @param stride The number of worldVertices entries between the value pairs written. + void computeWorldVertices(Slot &slot, float *worldVertices, size_t offset, size_t stride = 2); + + void computeWorldVertices(Slot &slot, Vector &worldVertices, size_t offset, size_t stride = 2); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getRotation(); + + void setRotation(float inValue); + + float getScaleX(); + + void setScaleX(float inValue); + + float getScaleY(); + + void setScaleY(float inValue); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + Color &getColor(); + + const String &getPath(); + + void setPath(const String &inValue); + + TextureRegion *getRegion(); + + void setRegion(TextureRegion *region); + + Sequence *getSequence(); + + void setSequence(Sequence *sequence); + + Vector &getOffset(); + + Vector &getUVs(); + + virtual Attachment *copy(); + + private: + static const int BLX; + static const int BLY; + static const int ULX; + static const int ULY; + static const int URX; + static const int URY; + static const int BRX; + static const int BRY; + + float _x, _y, _rotation, _scaleX, _scaleY, _width, _height; + Vector _vertexOffset; + Vector _uvs; + String _path; + Color _color; + TextureRegion *_region; + Sequence *_sequence; + }; +} + +#endif /* Spine_RegionAttachment_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RotateMode.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RotateMode.h new file mode 100644 index 0000000..fd3e373 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RotateMode.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RotateMode_h +#define Spine_RotateMode_h + +namespace spine { + enum RotateMode { + RotateMode_Tangent = 0, + RotateMode_Chain, + RotateMode_ChainScale + }; +} + +#endif /* Spine_RotateMode_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RotateTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RotateTimeline.h new file mode 100644 index 0000000..89b8621 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/RotateTimeline.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_RotateTimeline_h +#define Spine_RotateTimeline_h + +#include + +namespace spine { + class SP_API RotateTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AnimationState; + + RTTI_DECL + + public: + explicit RotateTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_RotateTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ScaleTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ScaleTimeline.h new file mode 100644 index 0000000..ef9f3ac --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ScaleTimeline.h @@ -0,0 +1,109 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ScaleTimeline_h +#define Spine_ScaleTimeline_h + +#include + +namespace spine { + class SP_API ScaleTimeline : public CurveTimeline2 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ScaleTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ScaleTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ScaleXTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ScaleXTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ScaleXTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ScaleYTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ScaleYTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ScaleYTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_ScaleTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Sequence.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Sequence.h new file mode 100644 index 0000000..8bf0fd7 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Sequence.h @@ -0,0 +1,98 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Sequence_h +#define Spine_Sequence_h + +#include +#include +#include + +namespace spine { + class Slot; + + class Attachment; + + class SkeletonBinary; + class SkeletonJson; + + class SP_API Sequence : public SpineObject { + friend class SkeletonBinary; + friend class SkeletonJson; + public: + Sequence(int count); + + ~Sequence(); + + Sequence *copy(); + + void apply(Slot *slot, Attachment *attachment); + + String getPath(const String &basePath, int index); + + int getId() { return _id; } + + void setId(int id) { _id = id; } + + int getStart() { return _start; } + + void setStart(int start) { _start = start; } + + int getDigits() { return _digits; } + + void setDigits(int digits) { _digits = digits; } + + int getSetupIndex() { return _setupIndex; } + + void setSetupIndex(int setupIndex) { _setupIndex = setupIndex; } + + Vector &getRegions() { return _regions; } + + private: + int _id; + Vector _regions; + int _start; + int _digits; + int _setupIndex; + + int getNextID(); + }; + + enum SequenceMode { + hold = 0, + once = 1, + loop = 2, + pingpong = 3, + onceReverse = 4, + loopReverse = 5, + pingpongReverse = 6 + }; +} + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SequenceTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SequenceTimeline.h new file mode 100644 index 0000000..7dd2ae3 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SequenceTimeline.h @@ -0,0 +1,73 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SequenceTimeline_h +#define Spine_SequenceTimeline_h + +#include +#include + +namespace spine { + class Attachment; + + class SP_API SequenceTimeline : public Timeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit SequenceTimeline(size_t frameCount, int slotIndex, spine::Attachment *attachment); + + virtual ~SequenceTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + void setFrame(int frame, float time, SequenceMode mode, int index, float delay); + + int getSlotIndex() { return _slotIndex; }; + + void setSlotIndex(int inValue) { _slotIndex = inValue; } + + Attachment *getAttachment() { return _attachment; } + + protected: + int _slotIndex; + Attachment *_attachment; + + static const int ENTRIES = 3; + static const int MODE = 1; + static const int DELAY = 2; + }; +} + +#endif /* Spine_SequenceTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ShearTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ShearTimeline.h new file mode 100644 index 0000000..6717743 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/ShearTimeline.h @@ -0,0 +1,109 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_ShearTimeline_h +#define Spine_ShearTimeline_h + +#include + +namespace spine { + class SP_API ShearTimeline : public CurveTimeline2 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ShearTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ShearTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ShearXTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ShearXTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ShearXTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API ShearYTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit ShearYTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~ShearYTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_ShearTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Skeleton.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Skeleton.h new file mode 100644 index 0000000..82304d6 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Skeleton.h @@ -0,0 +1,285 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Skeleton_h +#define Spine_Skeleton_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + class SkeletonData; + + class Bone; + + class Updatable; + + class Slot; + + class IkConstraint; + + class PathConstraint; + + class PhysicsConstraint; + + class TransformConstraint; + + class Skin; + + class Attachment; + + class SkeletonClipping; + + class SP_API Skeleton : public SpineObject { + friend class AnimationState; + + friend class SkeletonBounds; + + friend class SkeletonClipping; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ScaleXTimeline; + + friend class ScaleYTimeline; + + friend class ShearTimeline; + + friend class ShearXTimeline; + + friend class ShearYTimeline; + + friend class TransformConstraintTimeline; + + friend class RotateTimeline; + + friend class TranslateTimeline; + + friend class TranslateXTimeline; + + friend class TranslateYTimeline; + + friend class TwoColorTimeline; + + public: + explicit Skeleton(SkeletonData *skeletonData); + + ~Skeleton(); + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + void updateCache(); + + void printUpdateCache(); + + /// Updates the world transform for each bone and applies all constraints. + /// + /// See [World transforms](http://esotericsoftware.com/spine-runtime-skeletons#World-transforms) in the Spine + /// Runtimes Guide. + void updateWorldTransform(Physics physics); + + void updateWorldTransform(Physics physics, Bone *parent); + + /// Sets the bones, constraints, and slots to their setup pose values. + void setToSetupPose(); + + /// Sets the bones and constraints to their setup pose values. + void setBonesToSetupPose(); + + void setSlotsToSetupPose(); + + /// @return May be NULL. + Bone *findBone(const String &boneName); + + /// @return May be NULL. + Slot *findSlot(const String &slotName); + + /// Sets a skin by name (see setSkin). + void setSkin(const String &skinName); + + /// Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. + /// If there was no old skin, each slot's setup mode attachment is attached from the new skin. + /// After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling + /// See Skeleton::setSlotsToSetupPose() + /// Also, often AnimationState::apply(Skeleton&) is called before the next time the + /// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin. + /// @param newSkin May be NULL. + void setSkin(Skin *newSkin); + + /// @return May be NULL. + Attachment *getAttachment(const String &slotName, const String &attachmentName); + + /// @return May be NULL. + Attachment *getAttachment(int slotIndex, const String &attachmentName); + + /// @param attachmentName May be empty. + void setAttachment(const String &slotName, const String &attachmentName); + + /// @return May be NULL. + IkConstraint *findIkConstraint(const String &constraintName); + + /// @return May be NULL. + TransformConstraint *findTransformConstraint(const String &constraintName); + + /// @return May be NULL. + PathConstraint *findPathConstraint(const String &constraintName); + + /// @return May be NULL. + PhysicsConstraint *findPhysicsConstraint(const String &constraintName); + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// @param outX The horizontal distance between the skeleton origin and the left side of the AABB. + /// @param outY The vertical distance between the skeleton origin and the bottom side of the AABB. + /// @param outWidth The width of the AABB + /// @param outHeight The height of the AABB. + /// @param outVertexBuffer Reference to hold a Vector of floats. This method will assign it with new floats as needed. + // @param clipping Pointer to a SkeletonClipping instance or NULL. If a clipper is given, clipping attachments will be taken into account. + void getBounds(float &outX, float &outY, float &outWidth, float &outHeight, Vector &outVertexBuffer); + void getBounds(float &outX, float &outY, float &outWidth, float &outHeight, Vector &outVertexBuffer, SkeletonClipping *clipper); + + Bone *getRootBone(); + + SkeletonData *getData(); + + Vector &getBones(); + + Vector &getUpdateCacheList(); + + Vector &getSlots(); + + Vector &getDrawOrder(); + + Vector &getIkConstraints(); + + Vector &getPathConstraints(); + + Vector &getTransformConstraints(); + + Vector &getPhysicsConstraints(); + + Skin *getSkin(); + + Color &getColor(); + + void setPosition(float x, float y); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getScaleX(); + + void setScaleX(float inValue); + + float getScaleY(); + + void setScaleY(float inValue); + + float getTime(); + + void setTime(float time); + + void update(float delta); + + /// Rotates the physics constraint so next {@link #update(Physics)} forces are applied as if the bone rotated around the + /// specified point in world space. + void physicsTranslate(float x, float y); + + /// Calls {@link PhysicsConstraint#rotate(float, float, float)} for each physics constraint. */ + void physicsRotate(float x, float y, float degrees); + + private: + SkeletonData *_data; + Vector _bones; + Vector _slots; + Vector _drawOrder; + Vector _ikConstraints; + Vector _transformConstraints; + Vector _pathConstraints; + Vector _physicsConstraints; + Vector _updateCache; + Skin *_skin; + Color _color; + float _scaleX, _scaleY; + float _x, _y; + float _time; + + void sortIkConstraint(IkConstraint *constraint); + + void sortPathConstraint(PathConstraint *constraint); + + void sortPhysicsConstraint(PhysicsConstraint *constraint); + + void sortTransformConstraint(TransformConstraint *constraint); + + void sortPathConstraintAttachment(Skin *skin, size_t slotIndex, Bone &slotBone); + + void sortPathConstraintAttachment(Attachment *attachment, Bone &slotBone); + + void sortBone(Bone *bone); + + static void sortReset(Vector &bones); + }; +} + +#endif /* Spine_Skeleton_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonBinary.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonBinary.h new file mode 100644 index 0000000..cd74070 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonBinary.h @@ -0,0 +1,178 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonBinary_h +#define Spine_SkeletonBinary_h + +#include +#include +#include +#include +#include + +namespace spine { + class SkeletonData; + + class Atlas; + + class AttachmentLoader; + + class LinkedMesh; + + class Skin; + + class Attachment; + + class VertexAttachment; + + class Animation; + + class Timeline; + + class CurveTimeline; + + class CurveTimeline1; + + class CurveTimeline2; + + class Sequence; + + class SP_API SkeletonBinary : public SpineObject { + public: + static const int BONE_ROTATE = 0; + static const int BONE_TRANSLATE = 1; + static const int BONE_TRANSLATEX = 2; + static const int BONE_TRANSLATEY = 3; + static const int BONE_SCALE = 4; + static const int BONE_SCALEX = 5; + static const int BONE_SCALEY = 6; + static const int BONE_SHEAR = 7; + static const int BONE_SHEARX = 8; + static const int BONE_SHEARY = 9; + static const int BONE_INHERIT = 10; + + static const int SLOT_ATTACHMENT = 0; + static const int SLOT_RGBA = 1; + static const int SLOT_RGB = 2; + static const int SLOT_RGBA2 = 3; + static const int SLOT_RGB2 = 4; + static const int SLOT_ALPHA = 5; + + static const int ATTACHMENT_DEFORM = 0; + static const int ATTACHMENT_SEQUENCE = 1; + + static const int PATH_POSITION = 0; + static const int PATH_SPACING = 1; + static const int PATH_MIX = 2; + + static const int PHYSICS_INERTIA = 0; + static const int PHYSICS_STRENGTH = 1; + static const int PHYSICS_DAMPING = 2; + static const int PHYSICS_MASS = 4; + static const int PHYSICS_WIND = 5; + static const int PHYSICS_GRAVITY = 6; + static const int PHYSICS_MIX = 7; + static const int PHYSICS_RESET = 8; + + static const int CURVE_LINEAR = 0; + static const int CURVE_STEPPED = 1; + static const int CURVE_BEZIER = 2; + + explicit SkeletonBinary(Atlas *atlasArray); + + explicit SkeletonBinary(AttachmentLoader *attachmentLoader, bool ownsLoader = false); + + ~SkeletonBinary(); + + SkeletonData *readSkeletonData(const unsigned char *binary, int length); + + SkeletonData *readSkeletonDataFile(const String &path); + + void setScale(float scale) { _scale = scale; } + + String &getError() { return _error; } + + private: + struct DataInput : public SpineObject { + const unsigned char *cursor; + const unsigned char *end; + }; + + AttachmentLoader *_attachmentLoader; + Vector _linkedMeshes; + String _error; + float _scale; + const bool _ownsLoader; + + void setError(const char *value1, const char *value2); + + char *readString(DataInput *input); + + char *readStringRef(DataInput *input, SkeletonData *skeletonData); + + float readFloat(DataInput *input); + + unsigned char readByte(DataInput *input); + + signed char readSByte(DataInput *input); + + bool readBoolean(DataInput *input); + + int readInt(DataInput *input); + + void readColor(DataInput *input, Color &color); + + int readVarint(DataInput *input, bool optimizePositive); + + Skin *readSkin(DataInput *input, bool defaultSkin, SkeletonData *skeletonData, bool nonessential); + + Sequence *readSequence(DataInput *input); + + Attachment *readAttachment(DataInput *input, Skin *skin, int slotIndex, const String &attachmentName, + SkeletonData *skeletonData, bool nonessential); + + int readVertices(DataInput *input, Vector &vertices, Vector &bones, bool weighted); + + void readFloatArray(DataInput *input, int n, float scale, Vector &array); + + void readShortArray(DataInput *input, Vector &array, int n); + + Animation *readAnimation(const String &name, DataInput *input, SkeletonData *skeletonData); + + void + setBezier(DataInput *input, CurveTimeline *timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale); + + void readTimeline(DataInput *input, Vector &timelines, CurveTimeline1 *timeline, float scale); + + void readTimeline2(DataInput *input, Vector &timelines, CurveTimeline2 *timeline, float scale); + }; +} + +#endif /* Spine_SkeletonBinary_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonBounds.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonBounds.h new file mode 100644 index 0000000..00c5942 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonBounds.h @@ -0,0 +1,121 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonBounds_h +#define Spine_SkeletonBounds_h + +#include +#include +#include + +namespace spine { + class Skeleton; + + class BoundingBoxAttachment; + + class Polygon; + + /// Collects each BoundingBoxAttachment that is visible and computes the world vertices for its polygon. + /// The polygon vertices are provided along with convenience methods for doing hit detection. + class SP_API SkeletonBounds : public SpineObject { + public: + SkeletonBounds(); + + ~SkeletonBounds(); + + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// @param skeleton The skeleton. + /// @param updateAabb + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + void update(Skeleton &skeleton, bool updateAabb); + + /// Returns true if the axis aligned bounding box contains the point. + bool aabbcontainsPoint(float x, float y); + + /// Returns true if the axis aligned bounding box intersects the line segment. + bool aabbintersectsSegment(float x1, float y1, float x2, float y2); + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + bool aabbIntersectsSkeleton(SkeletonBounds &bounds); + + /// Returns true if the polygon contains the point. + bool containsPoint(Polygon *polygon, float x, float y); + + /// Returns the first bounding box attachment that contains the point, or NULL. When doing many checks, it is usually more + /// efficient to only call this method if {@link #aabbcontainsPoint(float, float)} returns true. + BoundingBoxAttachment *containsPoint(float x, float y); + + /// Returns the first bounding box attachment that contains the line segment, or NULL. When doing many checks, it is usually + /// more efficient to only call this method if {@link #aabbintersectsSegment(float, float, float, float)} returns true. + BoundingBoxAttachment *intersectsSegment(float x1, float y1, float x2, float y2); + + /// Returns true if the polygon contains the line segment. + bool intersectsSegment(Polygon *polygon, float x1, float y1, float x2, float y2); + + /// Returns the polygon for the given bounding box attachment or null if no + /// polygon can be found for the attachment. Requires a call to update() first. + Polygon *getPolygon(BoundingBoxAttachment *attachment); + + /// Returns the bounding box for the given polygon or null. Requires a call to update() first. + BoundingBoxAttachment * getBoundingBox(Polygon *polygon); + + /// Returns all polygons or an empty vector. Requires a call to update() first. + Vector &getPolygons(); + + /// Returns all bounding boxes. Requires a call to update() first. + Vector &getBoundingBoxes(); + + float getWidth(); + + float getHeight(); + + private: + Pool _polygonPool; + Vector _boundingBoxes; + Vector _polygons; + float _minX, _minY, _maxX, _maxY; + + void aabbCompute(); + }; + + class Polygon : public SpineObject { + public: + Vector _vertices; + int _count; + + Polygon() : _count(0) { + _vertices.ensureCapacity(16); + } + }; +} + +#endif /* Spine_SkeletonBounds_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonClipping.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonClipping.h new file mode 100644 index 0000000..52b868a --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonClipping.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonClipping_h +#define Spine_SkeletonClipping_h + +#include +#include + +namespace spine { + class Slot; + + class ClippingAttachment; + + class SP_API SkeletonClipping : public SpineObject { + public: + SkeletonClipping(); + + size_t clipStart(Slot &slot, ClippingAttachment *clip); + + void clipEnd(Slot &slot); + + void clipEnd(); + + void + clipTriangles(float *vertices, unsigned short *triangles, size_t trianglesLength); + + void + clipTriangles(float *vertices, unsigned short *triangles, size_t trianglesLength, float *uvs, size_t stride); + + void + clipTriangles(Vector &vertices, Vector &triangles, Vector &uvs, size_t stride); + + bool isClipping(); + + Vector &getClippedVertices(); + + Vector &getClippedTriangles(); + + Vector &getClippedUVs(); + + private: + Triangulator _triangulator; + Vector _clippingPolygon; + Vector _clipOutput; + Vector _clippedVertices; + Vector _clippedTriangles; + Vector _clippedUVs; + Vector _scratch; + ClippingAttachment *_clipAttachment; + Vector *> *_clippingPolygons; + + /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping + * area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */ + bool clip(float x1, float y1, float x2, float y2, float x3, float y3, Vector *clippingArea, + Vector *output); + + static void makeClockwise(Vector &polygon); + }; +} + +#endif /* Spine_SkeletonClipping_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonData.h new file mode 100644 index 0000000..71afa14 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonData.h @@ -0,0 +1,195 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonData_h +#define Spine_SkeletonData_h + +#include +#include + +namespace spine { + class BoneData; + + class SlotData; + + class Skin; + + class EventData; + + class Animation; + + class IkConstraintData; + + class TransformConstraintData; + + class PathConstraintData; + + class PhysicsConstraintData; + +/// Stores the setup pose and all of the stateless data for a skeleton. + class SP_API SkeletonData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class Skeleton; + + public: + SkeletonData(); + + ~SkeletonData(); + + /// Finds a bone by comparing each bone's name. + /// It is more efficient to cache the results of this method than to call it multiple times. + /// @return May be NULL. + BoneData *findBone(const String &boneName); + + /// @return May be NULL. + SlotData *findSlot(const String &slotName); + + /// @return May be NULL. + Skin *findSkin(const String &skinName); + + /// @return May be NULL. + spine::EventData *findEvent(const String &eventDataName); + + /// @return May be NULL. + Animation *findAnimation(const String &animationName); + + /// @return May be NULL. + IkConstraintData *findIkConstraint(const String &constraintName); + + /// @return May be NULL. + TransformConstraintData *findTransformConstraint(const String &constraintName); + + /// @return May be NULL. + PathConstraintData *findPathConstraint(const String &constraintName); + + /// @return May be NULL. + PhysicsConstraintData *findPhysicsConstraint(const String &constraintName); + + const String &getName(); + + void setName(const String &inValue); + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + Vector &getBones(); + + Vector &getSlots(); + + /// All skins, including the default skin. + Vector &getSkins(); + + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// @return May be NULL. + Skin *getDefaultSkin(); + + void setDefaultSkin(Skin *inValue); + + Vector &getEvents(); + + Vector &getAnimations(); + + Vector &getIkConstraints(); + + Vector &getTransformConstraints(); + + Vector &getPathConstraints(); + + Vector &getPhysicsConstraints(); + + float getX(); + + void setX(float inValue); + + float getY(); + + void setY(float inValue); + + float getWidth(); + + void setWidth(float inValue); + + float getHeight(); + + void setHeight(float inValue); + + float getReferenceScale(); + + void setReferenceScale(float inValue); + + /// The Spine version used to export this data, or NULL. + const String &getVersion(); + + void setVersion(const String &inValue); + + const String &getHash(); + + void setHash(const String &inValue); + + const String &getImagesPath(); + + void setImagesPath(const String &inValue); + + const String &getAudioPath(); + + void setAudioPath(const String &inValue); + + /// The dopesheet FPS in Spine. Available only when nonessential data was exported. + float getFps(); + + void setFps(float inValue); + + private: + String _name; + Vector _bones; // Ordered parents first + Vector _slots; // Setup pose draw order. + Vector _skins; + Skin *_defaultSkin; + Vector _events; + Vector _animations; + Vector _ikConstraints; + Vector _transformConstraints; + Vector _pathConstraints; + Vector _physicsConstraints; + float _x, _y, _width, _height; + float _referenceScale; + String _version; + String _hash; + Vector _strings; + + // Nonessential. + float _fps; + String _imagesPath; + String _audioPath; + }; +} + +#endif /* Spine_SkeletonData_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonJson.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonJson.h new file mode 100644 index 0000000..73ac5a0 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonJson.h @@ -0,0 +1,114 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonJson_h +#define Spine_SkeletonJson_h + +#include +#include +#include + +namespace spine { + class Timeline; + + class CurveTimeline; + + class CurveTimeline1; + + class CurveTimeline2; + + class VertexAttachment; + + class Animation; + + class Json; + + class SkeletonData; + + class Atlas; + + class AttachmentLoader; + + class LinkedMesh; + + class String; + + class Sequence; + + class SP_API SkeletonJson : public SpineObject { + public: + explicit SkeletonJson(Atlas *atlas); + + explicit SkeletonJson(AttachmentLoader *attachmentLoader, bool ownsLoader = false); + + ~SkeletonJson(); + + SkeletonData *readSkeletonDataFile(const String &path); + + SkeletonData *readSkeletonData(const char *json); + + void setScale(float scale) { _scale = scale; } + + String &getError() { return _error; } + + private: + AttachmentLoader *_attachmentLoader; + Vector _linkedMeshes; + float _scale; + const bool _ownsLoader; + String _error; + + static Sequence *readSequence(Json *sequence); + + static void + setBezier(CurveTimeline *timeline, int frame, int value, int bezier, float time1, float value1, float cx1, + float cy1, + float cx2, float cy2, float time2, float value2); + + static int + readCurve(Json *curve, CurveTimeline *timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale); + + static Timeline *readTimeline(Json *keyMap, CurveTimeline1 *timeline, float defaultValue, float scale); + + static Timeline * + readTimeline(Json *keyMap, CurveTimeline2 *timeline, const char *name1, const char *name2, float defaultValue, + float scale); + + Animation *readAnimation(Json *root, SkeletonData *skeletonData); + + void readVertices(Json *attachmentMap, VertexAttachment *attachment, size_t verticesLength); + + void setError(Json *root, const String &value1, const String &value2); + + int findSlotIndex(SkeletonData *skeletonData, const String &slotName, Vector timelines); + }; +} + +#endif /* Spine_SkeletonJson_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonRenderer.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonRenderer.h new file mode 100644 index 0000000..7da2b62 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SkeletonRenderer.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SkeletonRenderer_h +#define Spine_SkeletonRenderer_h + +#include +#include +#include + +namespace spine { + class Skeleton; + + struct SP_API RenderCommand { + float *positions; + float *uvs; + uint32_t *colors; + uint32_t *darkColors; + int32_t numVertices; + uint16_t *indices; + int32_t numIndices; + BlendMode blendMode; + void *texture; + RenderCommand *next; + }; + + class SP_API SkeletonRenderer: public SpineObject { + public: + explicit SkeletonRenderer(); + + ~SkeletonRenderer(); + + RenderCommand *render(Skeleton &skeleton); + private: + BlockAllocator _allocator; + Vector _worldVertices; + Vector _quadIndices; + SkeletonClipping _clipping; + Vector _renderCommands; + }; +} + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Skin.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Skin.h new file mode 100644 index 0000000..9b2383c --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Skin.h @@ -0,0 +1,171 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Skin_h +#define Spine_Skin_h + +#include +#include +#include + +namespace spine { + class Attachment; + + class Skeleton; + + class BoneData; + + class ConstraintData; + +/// Stores attachments by slot index and attachment name. +/// See SkeletonData::getDefaultSkin, Skeleton::getSkin, and +/// http://esotericsoftware.com/spine-runtime-skins in the Spine Runtimes Guide. + class SP_API Skin : public SpineObject { + friend class Skeleton; + + public: + class SP_API AttachmentMap : public SpineObject { + friend class Skin; + + public: + struct SP_API Entry { + size_t _slotIndex; + String _name; + Attachment *_attachment; + + Entry(size_t slotIndex, const String &name, Attachment *attachment) : + _slotIndex(slotIndex), + _name(name), + _attachment(attachment) { + } + }; + + class SP_API Entries { + friend class AttachmentMap; + + public: + bool hasNext() { + while (true) { + if (_slotIndex >= _buckets.size()) return false; + if (_bucketIndex >= _buckets[_slotIndex].size()) { + _bucketIndex = 0; + ++_slotIndex; + continue; + }; + return true; + } + } + + Entry &next() { + Entry &result = _buckets[_slotIndex][_bucketIndex]; + ++_bucketIndex; + return result; + } + + protected: + Entries(Vector > &buckets) : _buckets(buckets), _slotIndex(0), _bucketIndex(0) { + } + + private: + Vector > &_buckets; + size_t _slotIndex; + size_t _bucketIndex; + }; + + void put(size_t slotIndex, const String &attachmentName, Attachment *attachment); + + Attachment *get(size_t slotIndex, const String &attachmentName); + + void remove(size_t slotIndex, const String &attachmentName); + + Entries getEntries(); + + protected: + AttachmentMap(); + + private: + + int findInBucket(Vector &, const String &attachmentName); + + Vector > _buckets; + }; + + explicit Skin(const String &name); + + ~Skin(); + + /// Adds an attachment to the skin for the specified slot index and name. + /// If the name already exists for the slot, the previous value is replaced. + void setAttachment(size_t slotIndex, const String &name, Attachment *attachment); + + /// Returns the attachment for the specified slot index and name, or NULL. + Attachment *getAttachment(size_t slotIndex, const String &name); + + // Removes the attachment from the skin. + void removeAttachment(size_t slotIndex, const String &name); + + /// Finds the skin keys for a given slot. The results are added to the passed array of names. + /// @param slotIndex The target slotIndex. To find the slot index, use SkeletonData::findSlot and SlotData::getIndex. + /// @param names Found skin key names will be added to this array. + void findNamesForSlot(size_t slotIndex, Vector &names); + + /// Finds the attachments for a given slot. The results are added to the passed array of Attachments. + /// @param slotIndex The target slotIndex. To find the slot index, use SkeletonData::findSlot and SlotData::getIndex. + /// @param attachments Found Attachments will be added to this array. + void findAttachmentsForSlot(size_t slotIndex, Vector &attachments); + + const String &getName(); + + /// Adds all attachments, bones, and constraints from the specified skin to this skin. + void addSkin(Skin *other); + + /// Adds all attachments, bones, and constraints from the specified skin to this skin. Attachments are deep copied. + void copySkin(Skin *other); + + AttachmentMap::Entries getAttachments(); + + Vector &getBones(); + + Vector &getConstraints(); + + Color &getColor() { return _color; } + + private: + const String _name; + AttachmentMap _attachments; + Vector _bones; + Vector _constraints; + Color _color; + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + void attachAll(Skeleton &skeleton, Skin &oldSkin); + }; +} + +#endif /* Spine_Skin_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Slot.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Slot.h new file mode 100644 index 0000000..1c828ed --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Slot.h @@ -0,0 +1,137 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Slot_h +#define Spine_Slot_h + +#include +#include +#include + +namespace spine { + class SlotData; + + class Bone; + + class Skeleton; + + class Attachment; + + class SP_API Slot : public SpineObject { + friend class VertexAttachment; + + friend class Skeleton; + + friend class SkeletonBounds; + + friend class SkeletonClipping; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + + public: + Slot(SlotData &data, Bone &bone); + + void setToSetupPose(); + + SlotData &getData(); + + Bone &getBone(); + + Skeleton &getSkeleton(); + + Color &getColor(); + + Color &getDarkColor(); + + bool hasDarkColor(); + + /// May be NULL. + Attachment *getAttachment(); + + void setAttachment(Attachment *inValue); + + int getAttachmentState(); + + void setAttachmentState(int state); + + Vector &getDeform(); + + int getSequenceIndex(); + + void setSequenceIndex(int index); + + private: + SlotData &_data; + Bone &_bone; + Skeleton &_skeleton; + Color _color; + Color _darkColor; + bool _hasDarkColor; + Attachment *_attachment; + int _attachmentState; + int _sequenceIndex; + Vector _deform; + }; +} + +#endif /* Spine_Slot_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SlotData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SlotData.h new file mode 100644 index 0000000..8e16ea9 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SlotData.h @@ -0,0 +1,126 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SlotData_h +#define Spine_SlotData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API SlotData : public SpineObject { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class AttachmentTimeline; + + friend class RGBATimeline; + + friend class RGBTimeline; + + friend class AlphaTimeline; + + friend class RGBA2Timeline; + + friend class RGB2Timeline; + + friend class DeformTimeline; + + friend class DrawOrderTimeline; + + friend class EventTimeline; + + friend class IkConstraintTimeline; + + friend class PathConstraintMixTimeline; + + friend class PathConstraintPositionTimeline; + + friend class PathConstraintSpacingTimeline; + + friend class ScaleTimeline; + + friend class ShearTimeline; + + friend class TransformConstraintTimeline; + + friend class TranslateTimeline; + + friend class TwoColorTimeline; + + public: + SlotData(int index, const String &name, BoneData &boneData); + + int getIndex(); + + const String &getName(); + + BoneData &getBoneData(); + + Color &getColor(); + + Color &getDarkColor(); + + bool hasDarkColor(); + + void setHasDarkColor(bool inValue); + + /// May be empty. + const String &getAttachmentName(); + + void setAttachmentName(const String &inValue); + + BlendMode getBlendMode(); + + void setBlendMode(BlendMode inValue); + + bool isVisible(); + + void setVisible(bool inValue); + + private: + const int _index; + String _name; + BoneData &_boneData; + Color _color; + Color _darkColor; + + bool _hasDarkColor; + String _attachmentName; + BlendMode _blendMode; + bool _visible; + }; +} + +#endif /* Spine_SlotData_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SpacingMode.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SpacingMode.h new file mode 100644 index 0000000..84a311b --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SpacingMode.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_SpacingMode_h +#define Spine_SpacingMode_h + +namespace spine { + enum SpacingMode { + SpacingMode_Length = 0, + SpacingMode_Fixed, + SpacingMode_Percent, + SpacingMode_Proportional + }; +} + +#endif /* Spine_SpacingMode_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SpineObject.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SpineObject.h new file mode 100644 index 0000000..dc89369 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SpineObject.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Object_h +#define Spine_Object_h + +#include +#include + +#include + +namespace spine { + class String; + + class SP_API SpineObject { + public: + void *operator new(size_t sz); + + void *operator new(size_t sz, const char *file, int line); + + void *operator new(size_t sz, void *ptr); + + void operator delete(void *p, const char *file, int line); + + void operator delete(void *p, void *mem); + + void operator delete(void *p); + + virtual ~SpineObject(); + }; +} + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SpineString.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SpineString.h new file mode 100644 index 0000000..3f77431 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/SpineString.h @@ -0,0 +1,246 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_STRING_H +#define SPINE_STRING_H + +#include +#include + +#include +#include + +namespace spine { + class SP_API String : public SpineObject { + public: + String() : _length(0), _buffer(NULL), _tempowner(true) { + } + + String(const char *chars, bool own = false, bool tofree = true) { + _tempowner = tofree; + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + if (!own) { + _buffer = SpineExtension::calloc(_length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, chars, _length + 1); + } else { + _buffer = (char *) chars; + } + } + } + + String(const String &other) { + _tempowner = true; + if (!other._buffer) { + _length = 0; + _buffer = NULL; + } else { + _length = other._length; + _buffer = SpineExtension::calloc(other._length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, other._buffer, other._length + 1); + } + } + + size_t length() const { + return _length; + } + + bool isEmpty() const { + return _length == 0; + } + + const char *buffer() const { + return _buffer; + } + + void own(const String &other) { + if (this == &other) return; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + _length = other._length; + _buffer = other._buffer; + other._length = 0; + other._buffer = NULL; + } + + void own(const char *chars) { + if (_buffer == chars) return; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + _buffer = (char *) chars; + } + } + + void unown() { + _length = 0; + _buffer = NULL; + } + + String &operator=(const String &other) { + if (this == &other) return *this; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + if (!other._buffer) { + _length = 0; + _buffer = NULL; + } else { + _length = other._length; + _buffer = SpineExtension::calloc(other._length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, other._buffer, other._length + 1); + } + return *this; + } + + String &operator=(const char *chars) { + if (_buffer == chars) return *this; + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + if (!chars) { + _length = 0; + _buffer = NULL; + } else { + _length = strlen(chars); + _buffer = SpineExtension::calloc(_length + 1, __FILE__, __LINE__); + memcpy((void *) _buffer, chars, _length + 1); + } + return *this; + } + + String &append(const char *chars) { + size_t len = strlen(chars); + size_t thisLen = _length; + _length = _length + len; + bool same = chars == _buffer; + _buffer = SpineExtension::realloc(_buffer, _length + 1, __FILE__, __LINE__); + memcpy((void *) (_buffer + thisLen), (void *) (same ? _buffer : chars), len + 1); + return *this; + } + + String &append(const String &other) { + size_t len = other.length(); + size_t thisLen = _length; + _length = _length + len; + bool same = other._buffer == _buffer; + _buffer = SpineExtension::realloc(_buffer, _length + 1, __FILE__, __LINE__); + memcpy((void *) (_buffer + thisLen), (void *) (same ? _buffer : other._buffer), len + 1); + return *this; + } + + String &append(int other) { + char str[100]; + snprintf(str, 100, "%i", other); + append(str); + return *this; + } + + String &append(float other) { + char str[100]; + snprintf(str, 100, "%f", other); + append(str); + return *this; + } + + bool startsWith(const String &needle) const { + if (needle.length() > length()) return false; + for (int i = 0; i < (int)needle.length(); i++) { + if (buffer()[i] != needle.buffer()[i]) return false; + } + return true; + } + + int lastIndexOf(const char c) const { + for (int i = (int)length() - 1; i >= 0; i--) { + if (buffer()[i] == c) return i; + } + return -1; + } + + String substring(int startIndex, int length) const { + if (startIndex < 0 || startIndex >= (int)_length || length < 0 || startIndex + length > (int)_length) { + return String(); + } + char* subStr = SpineExtension::calloc(length + 1, __FILE__, __LINE__); + memcpy(subStr, _buffer + startIndex, length); + subStr[length] = '\0'; + return String(subStr, true, true); + } + + String substring(int startIndex) const { + if (startIndex < 0 || startIndex >= (int)_length) { + return String(); + } + int length = (int)_length - startIndex; + char* subStr = SpineExtension::calloc(length + 1, __FILE__, __LINE__); + memcpy(subStr, _buffer + startIndex, length); + subStr[length] = '\0'; + return String(subStr, true, true); + } + + friend bool operator==(const String &a, const String &b) { + if (a._buffer == b._buffer) return true; + if (a._length != b._length) return false; + if (a._buffer && b._buffer) { + return strcmp(a._buffer, b._buffer) == 0; + } else { + return false; + } + } + + friend bool operator!=(const String &a, const String &b) { + return !(a == b); + } + + ~String() { + if (_buffer && _tempowner) { + SpineExtension::free(_buffer, __FILE__, __LINE__); + } + } + + private: + mutable size_t _length; + mutable char *_buffer; + mutable bool _tempowner; + }; +} + + +#endif //SPINE_STRING_H diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TextureLoader.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TextureLoader.h new file mode 100644 index 0000000..60f86af --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TextureLoader.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TextureLoader_h +#define Spine_TextureLoader_h + +#include +#include + +namespace spine { + class AtlasPage; + + class SP_API TextureLoader : public SpineObject { + public: + TextureLoader(); + + virtual ~TextureLoader(); + + virtual void load(AtlasPage &page, const String &path) = 0; + + virtual void unload(void *texture) = 0; + }; +} + +#endif /* Spine_TextureLoader_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TextureRegion.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TextureRegion.h new file mode 100644 index 0000000..1082c5d --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TextureRegion.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TextureRegion_h +#define Spine_TextureRegion_h + +#include + +namespace spine { + class SP_API TextureRegion : public SpineObject { + public: + void *rendererObject; + float u, v, u2, v2; + int degrees; + float offsetX, offsetY; + int width, height; + int originalWidth, originalHeight; + + TextureRegion(): rendererObject(NULL), u(0), v(0), u2(0), v2(0), degrees(0), offsetX(0), offsetY(0), width(0), height(0), originalWidth(0), originalHeight(0) {}; + ~TextureRegion() {}; + }; +} + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Timeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Timeline.h new file mode 100644 index 0000000..c9ded4b --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Timeline.h @@ -0,0 +1,86 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Timeline_h +#define Spine_Timeline_h + +#include +#include +#include +#include +#include +#include + +namespace spine { + class Skeleton; + + class Event; + + class SP_API Timeline : public SpineObject { + RTTI_DECL + + public: + Timeline(size_t frameCount, size_t frameEntries); + + virtual ~Timeline(); + + /// Sets the value(s) for the specified time. + /// @param skeleton The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. + /// @param lastTime lastTime The time this timeline was last applied. Timelines such as EventTimeline trigger only at specific times rather than every frame. In that case, the timeline triggers everything between lastTime (exclusive) and time (inclusive). + /// @param time The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. + /// @param pEvents If any events are fired, they are added to this array. Can be NULL to ignore firing events or if the timeline does not fire events. May be NULL. + /// @param alpha alpha 0 applies the current or setup pose value (depending on pose parameter). 1 applies the timeline + /// value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting alpha over + /// time, an animation can be mixed in or out. alpha can also be useful to apply animations on top of each other (layered). + /// @param blend Controls how mixing is applied when alpha is than 1. + /// @param direction Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction) = 0; + + size_t getFrameEntries(); + + size_t getFrameCount(); + + Vector &getFrames(); + + float getDuration(); + + virtual Vector &getPropertyIds(); + + protected: + void setPropertyIds(PropertyId propertyIds[], size_t propertyIdsCount); + + Vector _propertyIds; + Vector _frames; + size_t _frameEntries; + }; +} + +#endif /* Spine_Timeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TransformConstraint.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TransformConstraint.h new file mode 100644 index 0000000..cc85902 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TransformConstraint.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformConstraint_h +#define Spine_TransformConstraint_h + +#include + +#include + +namespace spine { + class TransformConstraintData; + + class Skeleton; + + class Bone; + + class SP_API TransformConstraint : public Updatable { + friend class Skeleton; + + friend class TransformConstraintTimeline; + + RTTI_DECL + + public: + TransformConstraint(TransformConstraintData &data, Skeleton &skeleton); + + virtual void update(Physics physics); + + virtual int getOrder(); + + TransformConstraintData &getData(); + + Vector &getBones(); + + Bone *getTarget(); + + void setTarget(Bone *inValue); + + float getMixRotate(); + + void setMixRotate(float inValue); + + float getMixX(); + + void setMixX(float inValue); + + float getMixY(); + + void setMixY(float inValue); + + float getMixScaleX(); + + void setMixScaleX(float inValue); + + float getMixScaleY(); + + void setMixScaleY(float inValue); + + float getMixShearY(); + + void setMixShearY(float inValue); + + bool isActive(); + + void setActive(bool inValue); + + void setToSetupPose(); + + private: + TransformConstraintData &_data; + Vector _bones; + Bone *_target; + float _mixRotate, _mixX, _mixY, _mixScaleX, _mixScaleY, _mixShearY; + bool _active; + + void applyAbsoluteWorld(); + + void applyRelativeWorld(); + + void applyAbsoluteLocal(); + + void applyRelativeLocal(); + }; +} + +#endif /* Spine_TransformConstraint_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TransformConstraintData.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TransformConstraintData.h new file mode 100644 index 0000000..9446bf8 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TransformConstraintData.h @@ -0,0 +1,128 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformConstraintData_h +#define Spine_TransformConstraintData_h + +#include +#include +#include +#include + +namespace spine { + class BoneData; + + class SP_API TransformConstraintData : public ConstraintData { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class TransformConstraint; + + friend class Skeleton; + + friend class TransformConstraintTimeline; + + public: + RTTI_DECL + + explicit TransformConstraintData(const String &name); + + Vector &getBones(); + + BoneData *getTarget(); + + void setTarget(BoneData *target); + + float getMixRotate(); + + void setMixRotate(float mixRotate); + + float getMixX(); + + void setMixX(float mixX); + + float getMixY(); + + void setMixY(float mixY); + + float getMixScaleX(); + + void setMixScaleX(float mixScaleX); + + float getMixScaleY(); + + void setMixScaleY(float mixScaleY); + + float getMixShearY(); + + void setMixShearY(float mixShearY); + + float getOffsetRotation(); + + void setOffsetRotation(float offsetRotation); + + float getOffsetX(); + + void setOffsetX(float offsetX); + + float getOffsetY(); + + void setOffsetY(float offsetY); + + float getOffsetScaleX(); + + void setOffsetScaleX(float offsetScaleX); + + float getOffsetScaleY(); + + void setOffsetScaleY(float offsetScaleY); + + float getOffsetShearY(); + + void setOffsetShearY(float offsetShearY); + + bool isRelative(); + + void setRelative(bool isRelative); + + bool isLocal(); + + void setLocal(bool isLocal); + + private: + Vector _bones; + BoneData *_target; + float _mixRotate, _mixX, _mixY, _mixScaleX, _mixScaleY, _mixShearY; + float _offsetRotation, _offsetX, _offsetY, _offsetScaleX, _offsetScaleY, _offsetShearY; + bool _relative, _local; + }; +} + +#endif /* Spine_TransformConstraintData_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TransformConstraintTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TransformConstraintTimeline.h new file mode 100644 index 0000000..514c6d3 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TransformConstraintTimeline.h @@ -0,0 +1,71 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TransformConstraintTimeline_h +#define Spine_TransformConstraintTimeline_h + +#include + +namespace spine { + + class SP_API TransformConstraintTimeline : public CurveTimeline { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TransformConstraintTimeline(size_t frameCount, size_t bezierCount, int transformConstraintIndex); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + void setFrame(size_t frameIndex, float time, float mixRotate, float mixX, float mixY, float mixScaleX, + float mixScaleY, float mixShearY); + + int getTransformConstraintIndex() { return _constraintIndex; } + + void setTransformConstraintIndex(int inValue) { _constraintIndex = inValue; } + + private: + int _constraintIndex; + + static const int ENTRIES = 7; + static const int ROTATE = 1; + static const int X = 2; + static const int Y = 3; + static const int SCALEX = 4; + static const int SCALEY = 5; + static const int SHEARY = 6; + }; +} + +#endif /* Spine_TransformConstraintTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TranslateTimeline.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TranslateTimeline.h new file mode 100644 index 0000000..77dbf7e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/TranslateTimeline.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_TranslateTimeline_h +#define Spine_TranslateTimeline_h + +#include + +#include +#include + +namespace spine { + + class SP_API TranslateTimeline : public CurveTimeline2 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TranslateTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~TranslateTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API TranslateXTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TranslateXTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~TranslateXTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; + + class SP_API TranslateYTimeline : public CurveTimeline1 { + friend class SkeletonBinary; + + friend class SkeletonJson; + + RTTI_DECL + + public: + explicit TranslateYTimeline(size_t frameCount, size_t bezierCount, int boneIndex); + + virtual ~TranslateYTimeline(); + + virtual void + apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, MixBlend blend, + MixDirection direction); + + int getBoneIndex() { return _boneIndex; } + + void setBoneIndex(int inValue) { _boneIndex = inValue; } + + private: + int _boneIndex; + }; +} + +#endif /* Spine_TranslateTimeline_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Triangulator.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Triangulator.h new file mode 100644 index 0000000..801167d --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Triangulator.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Triangulator_h +#define Spine_Triangulator_h + +#include +#include + +namespace spine { + class SP_API Triangulator : public SpineObject { + public: + ~Triangulator(); + + Vector &triangulate(Vector &vertices); + + Vector* > & + decompose(Vector + &vertices, + Vector &triangles + ); + + private: + Vector* > + _convexPolygons; + Vector* > + _convexPolygonsIndices; + + Vector _indices; + Vector _isConcaveArray; + Vector _triangles; + + Pool > _polygonPool; + Pool > _polygonIndicesPool; + + static bool isConcave(int index, int vertexCount, Vector &vertices, Vector &indices); + + static bool positiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y); + + static int winding(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y); + }; +} + +#endif /* Spine_Triangulator_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Updatable.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Updatable.h new file mode 100644 index 0000000..dc5b4d3 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Updatable.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Updatable_h +#define Spine_Updatable_h + +#include +#include +#include + +namespace spine { + class SP_API Updatable : public SpineObject { + RTTI_DECL + + public: + Updatable(); + + virtual ~Updatable(); + + virtual void update(Physics physics) = 0; + + virtual bool isActive() = 0; + + virtual void setActive(bool inValue) = 0; + }; +} + +#endif /* Spine_Updatable_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Vector.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Vector.h new file mode 100644 index 0000000..8ad9196 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Vector.h @@ -0,0 +1,245 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Vector_h +#define Spine_Vector_h + +#include +#include +#include +#include + +namespace spine { + template + class SP_API Vector : public SpineObject { + public: + using size_type = size_t; + using value_type = T; + + Vector() : _size(0), _capacity(0), _buffer(NULL) { + } + + Vector(const Vector &inVector) : _size(inVector._size), _capacity(inVector._capacity), _buffer(NULL) { + if (_capacity > 0) { + _buffer = allocate(_capacity); + for (size_t i = 0; i < _size; ++i) { + construct(_buffer + i, inVector._buffer[i]); + } + } + } + + ~Vector() { + clear(); + deallocate(_buffer); + } + + inline void clear() { + for (size_t i = 0; i < _size; ++i) { + destroy(_buffer + (_size - 1 - i)); + } + + _size = 0; + } + + inline size_t getCapacity() const { + return _capacity; + } + + inline size_t size() const { + return _size; + } + + inline void setSize(size_t newSize, const T &defaultValue) { + assert(newSize >= 0); + size_t oldSize = _size; + _size = newSize; + if (_capacity < newSize) { + if (_capacity == 0) { + _capacity = _size; + } else { + _capacity = (int) (_size * 1.75f); + } + if (_capacity < 8) _capacity = 8; + _buffer = spine::SpineExtension::realloc(_buffer, _capacity, __FILE__, __LINE__); + } + if (oldSize < _size) { + for (size_t i = oldSize; i < _size; i++) { + construct(_buffer + i, defaultValue); + } + } else { + for (size_t i = _size; i < oldSize; i++) { + destroy(_buffer + i); + } + } + } + + inline void ensureCapacity(size_t newCapacity = 0) { + if (_capacity >= newCapacity) return; + _capacity = newCapacity; + _buffer = SpineExtension::realloc(_buffer, newCapacity, __FILE__, __LINE__); + } + + inline void add(const T &inValue) { + if (_size == _capacity) { + // inValue might reference an element in this buffer + // When we reallocate, the reference becomes invalid. + // We thus need to create a defensive copy before + // reallocating. + T valueCopy = inValue; + _capacity = (int) (_size * 1.75f); + if (_capacity < 8) _capacity = 8; + _buffer = spine::SpineExtension::realloc(_buffer, _capacity, __FILE__, __LINE__); + construct(_buffer + _size++, valueCopy); + } else { + construct(_buffer + _size++, inValue); + } + } + + inline void addAll(const Vector &inValue) { + ensureCapacity(this->size() + inValue.size()); + for (size_t i = 0; i < inValue.size(); i++) { + add(inValue[i]); + } + } + + inline void clearAndAddAll(const Vector &inValue) { + this->clear(); + this->addAll(inValue); + } + + inline void removeAt(size_t inIndex) { + assert(inIndex < _size); + + --_size; + + if (inIndex != _size) { + for (size_t i = inIndex; i < _size; ++i) { + T tmp(_buffer[i]); + _buffer[i] = _buffer[i + 1]; + _buffer[i + 1] = tmp; + } + } + + destroy(_buffer + _size); + } + + inline bool contains(const T &inValue) { + for (size_t i = 0; i < _size; ++i) { + if (_buffer[i] == inValue) { + return true; + } + } + + return false; + } + + inline int indexOf(const T &inValue) { + for (size_t i = 0; i < _size; ++i) { + if (_buffer[i] == inValue) { + return (int) i; + } + } + + return -1; + } + + inline T &operator[](size_t inIndex) { + assert(inIndex < _size); + + return _buffer[inIndex]; + } + + inline const T &operator[](size_t inIndex) const { + assert(inIndex < _size); + + return _buffer[inIndex]; + } + + inline friend bool operator==(Vector &lhs, Vector &rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (size_t i = 0, n = lhs.size(); i < n; ++i) { + if (lhs[i] != rhs[i]) { + return false; + } + } + + return true; + } + + inline friend bool operator!=(Vector &lhs, Vector &rhs) { + return !(lhs == rhs); + } + + Vector &operator=(const Vector &inVector) { + if (this != &inVector) { + clearAndAddAll(inVector); + } + return *this; + } + + inline T *buffer() { + return _buffer; + } + + private: + size_t _size; + size_t _capacity; + T *_buffer; + + inline T *allocate(size_t n) { + assert(n > 0); + + T *ptr = SpineExtension::calloc(n, __FILE__, __LINE__); + + assert(ptr); + + return ptr; + } + + inline void deallocate(T *buffer) { + if (_buffer) { + SpineExtension::free(buffer, __FILE__, __LINE__); + } + } + + inline void construct(T *buffer, const T &val) { + new(buffer) T(val); + } + + inline void destroy(T *buffer) { + buffer->~T(); + } + + }; +} + +#endif /* Spine_Vector_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Version.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Version.h new file mode 100644 index 0000000..eaa5655 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Version.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_VERSION_H_ +#define SPINE_VERSION_H_ + +#define SPINE_MAJOR_VERSION 4 +#define SPINE_MINOR_VERSION 2 +#define SPINE_VERSION_STRING "4.2" + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/VertexAttachment.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/VertexAttachment.h new file mode 100644 index 0000000..e4957a9 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/VertexAttachment.h @@ -0,0 +1,101 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_VertexAttachment_h +#define Spine_VertexAttachment_h + +#include + +#include + +namespace spine { + class Slot; + + /// An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. + class SP_API VertexAttachment : public Attachment { + friend class SkeletonBinary; + + friend class SkeletonJson; + + friend class DeformTimeline; + + RTTI_DECL + + public: + explicit VertexAttachment(const String &name); + + virtual ~VertexAttachment(); + + void computeWorldVertices(Slot &slot, float *worldVertices); + + void computeWorldVertices(Slot &slot, Vector &worldVertices); + + /// Transforms local vertices to world coordinates. + /// @param start The index of the first Vertices value to transform. Each vertex has 2 values, x and y. + /// @param count The number of world vertex values to output. Must be less than or equal to WorldVerticesLength - start. + /// @param worldVertices The output world vertices. Must have a length greater than or equal to offset + count. + /// @param offset The worldVertices index to begin writing values. + /// @param stride The number of worldVertices entries between the value pairs written. + virtual void computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride = 2); + + virtual void computeWorldVertices(Slot &slot, size_t start, size_t count, Vector &worldVertices, size_t offset, + size_t stride = 2); + + /// Gets a unique ID for this attachment. + int getId(); + + Vector &getBones(); + + Vector &getVertices(); + + size_t getWorldVerticesLength(); + + void setWorldVerticesLength(size_t inValue); + + Attachment * getTimelineAttachment(); + + void setTimelineAttachment(Attachment *attachment); + + void copyTo(VertexAttachment *other); + + protected: + Vector _bones; + Vector _vertices; + size_t _worldVerticesLength; + Attachment *_timelineAttachment; + + private: + const int _id; + + static int getNextID(); + }; +} + +#endif /* Spine_VertexAttachment_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Vertices.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Vertices.h new file mode 100644 index 0000000..070d823 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/Vertices.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef Spine_Vertices_h +#define Spine_Vertices_h + +#include + +namespace spine { + class SP_API Vertices : public SpineObject { + public: + Vector _bones; + Vector _vertices; + }; +} + +#endif /* Spine_Vertices_h */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/dll.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/dll.h new file mode 100644 index 0000000..2681a53 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/dll.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_SHAREDLIB_H +#define SPINE_SHAREDLIB_H + +#ifdef _WIN32 +#define DLLIMPORT __declspec(dllimport) +#define DLLEXPORT __declspec(dllexport) +#else +#ifndef DLLIMPORT +#define DLLIMPORT +#endif +#ifndef DLLEXPORT +#define DLLEXPORT +#endif +#endif + +#ifdef SPINEPLUGIN_API +#define SP_API SPINEPLUGIN_API +#else +#define SP_API +#endif + +#endif /* SPINE_SHAREDLIB_H */ diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/spine.h b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/spine.h new file mode 100644 index 0000000..ab3e541 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/include/spine/spine.h @@ -0,0 +1,115 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef SPINE_SPINE_H_ +#define SPINE_SPINE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Animation.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Animation.cpp new file mode 100644 index 0000000..21fafac --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Animation.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include + +using namespace spine; + +Animation::Animation(const String &name, Vector &timelines, float duration) : _timelines(timelines), + _timelineIds(), + _duration(duration), + _name(name) { + assert(_name.length() > 0); + for (size_t i = 0; i < timelines.size(); i++) { + Vector propertyIds = timelines[i]->getPropertyIds(); + for (size_t ii = 0; ii < propertyIds.size(); ii++) + _timelineIds.put(propertyIds[ii], true); + } +} + +bool Animation::hasTimeline(Vector &ids) { + for (size_t i = 0; i < ids.size(); i++) { + if (_timelineIds.containsKey(ids[i])) return true; + } + return false; +} + +Animation::~Animation() { + ContainerUtil::cleanUpVectorOfPointers(_timelines); +} + +void Animation::apply(Skeleton &skeleton, float lastTime, float time, bool loop, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + if (loop && _duration != 0) { + time = MathUtil::fmod(time, _duration); + if (lastTime > 0) { + lastTime = MathUtil::fmod(lastTime, _duration); + } + } + + for (size_t i = 0, n = _timelines.size(); i < n; ++i) { + _timelines[i]->apply(skeleton, lastTime, time, pEvents, alpha, blend, direction); + } +} + +const String &Animation::getName() { + return _name; +} + +Vector &Animation::getTimelines() { + return _timelines; +} + +float Animation::getDuration() { + return _duration; +} + +void Animation::setDuration(float inValue) { + _duration = inValue; +} + +int Animation::search(Vector &frames, float target) { + size_t n = (int) frames.size(); + for (size_t i = 1; i < n; i++) { + if (frames[i] > target) return (int) (i - 1); + } + return (int) (n - 1); +} + +int Animation::search(Vector &frames, float target, int step) { + size_t n = frames.size(); + for (size_t i = step; i < n; i += step) + if (frames[i] > target) return (int) (i - step); + return (int) (n - step); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AnimationState.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AnimationState.cpp new file mode 100644 index 0000000..7920e94 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AnimationState.cpp @@ -0,0 +1,1104 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +void dummyOnAnimationEventFunc(AnimationState *state, spine::EventType type, TrackEntry *entry, Event *event = NULL) { + SP_UNUSED(state); + SP_UNUSED(type); + SP_UNUSED(entry); + SP_UNUSED(event); +} + +TrackEntry::TrackEntry() : _animation(NULL), _previous(NULL), _next(NULL), _mixingFrom(NULL), _mixingTo(0), + _trackIndex(0), _loop(false), _holdPrevious(false), _reverse(false), + _shortestRotation(false), + _eventThreshold(0), _mixAttachmentThreshold(0), _alphaAttachmentThreshold(0), _mixDrawOrderThreshold(0), _animationStart(0), + _animationEnd(0), _animationLast(0), _nextAnimationLast(0), _delay(0), _trackTime(0), + _trackLast(0), _nextTrackLast(0), _trackEnd(0), _timeScale(1.0f), _alpha(0), _mixTime(0), + _mixDuration(0), _interruptAlpha(0), _totalAlpha(0), _mixBlend(MixBlend_Replace), + _listener(dummyOnAnimationEventFunc), _listenerObject(NULL) { +} + +TrackEntry::~TrackEntry() {} + +int TrackEntry::getTrackIndex() { return _trackIndex; } + +Animation *TrackEntry::getAnimation() { return _animation; } + +TrackEntry *TrackEntry::getPrevious() { return _previous; } + +bool TrackEntry::getLoop() { return _loop; } + +void TrackEntry::setLoop(bool inValue) { _loop = inValue; } + +bool TrackEntry::getHoldPrevious() { return _holdPrevious; } + +void TrackEntry::setHoldPrevious(bool inValue) { _holdPrevious = inValue; } + +bool TrackEntry::getReverse() { return _reverse; } + +void TrackEntry::setReverse(bool inValue) { _reverse = inValue; } + +bool TrackEntry::getShortestRotation() { return _shortestRotation; } + +void TrackEntry::setShortestRotation(bool inValue) { _shortestRotation = inValue; } + +float TrackEntry::getDelay() { return _delay; } + +void TrackEntry::setDelay(float inValue) { _delay = inValue; } + +float TrackEntry::getTrackTime() { return _trackTime; } + +void TrackEntry::setTrackTime(float inValue) { _trackTime = inValue; } + +float TrackEntry::getTrackEnd() { return _trackEnd; } + +void TrackEntry::setTrackEnd(float inValue) { _trackEnd = inValue; } + +float TrackEntry::getAnimationStart() { return _animationStart; } + +void TrackEntry::setAnimationStart(float inValue) { _animationStart = inValue; } + +float TrackEntry::getAnimationEnd() { return _animationEnd; } + +void TrackEntry::setAnimationEnd(float inValue) { _animationEnd = inValue; } + +float TrackEntry::getAnimationLast() { return _animationLast; } + +void TrackEntry::setAnimationLast(float inValue) { + _animationLast = inValue; + _nextAnimationLast = inValue; +} + +float TrackEntry::getAnimationTime() { + if (_loop) { + float duration = _animationEnd - _animationStart; + if (duration == 0) return _animationStart; + return MathUtil::fmod(_trackTime, duration) + _animationStart; + } + + return MathUtil::min(_trackTime + _animationStart, _animationEnd); +} + +float TrackEntry::getTimeScale() { return _timeScale; } + +void TrackEntry::setTimeScale(float inValue) { _timeScale = inValue; } + +float TrackEntry::getAlpha() { return _alpha; } + +void TrackEntry::setAlpha(float inValue) { _alpha = inValue; } + +float TrackEntry::getEventThreshold() { return _eventThreshold; } + +void TrackEntry::setEventThreshold(float inValue) { _eventThreshold = inValue; } + +float TrackEntry::getMixAttachmentThreshold() { return _mixAttachmentThreshold; } + +void TrackEntry::setMixAttachmentThreshold(float inValue) { _mixAttachmentThreshold = inValue; } + +float TrackEntry::getAlphaAttachmentThreshold() { return _alphaAttachmentThreshold; } + +void TrackEntry::setAlphaAttachmentThreshold(float inValue) { _alphaAttachmentThreshold = inValue; } + +float TrackEntry::getMixDrawOrderThreshold() { return _mixDrawOrderThreshold; } + +void TrackEntry::setMixDrawOrderThreshold(float inValue) { _mixDrawOrderThreshold = inValue; } + +TrackEntry *TrackEntry::getNext() { return _next; } + +bool TrackEntry::isComplete() { + return _trackTime >= _animationEnd - _animationStart; +} + +float TrackEntry::getMixTime() { return _mixTime; } + +void TrackEntry::setMixTime(float inValue) { _mixTime = inValue; } + +float TrackEntry::getMixDuration() { return _mixDuration; } + +void TrackEntry::setMixDuration(float inValue) { _mixDuration = inValue; } + +void TrackEntry::setMixDuration(float mixDuration, float delay) { + _mixDuration = mixDuration; + if (delay <= 0) { + if (_previous != nullptr) + delay = MathUtil::max(delay + _previous->getTrackComplete() - mixDuration, 0.0f); + else + delay = 0; + } + this->_delay = delay; +} + +TrackEntry *TrackEntry::getMixingFrom() { return _mixingFrom; } + +TrackEntry *TrackEntry::getMixingTo() { return _mixingTo; } + +void TrackEntry::setMixBlend(MixBlend blend) { _mixBlend = blend; } + +MixBlend TrackEntry::getMixBlend() { return _mixBlend; } + +void TrackEntry::resetRotationDirections() { + _timelinesRotation.clear(); +} + +void TrackEntry::setListener(AnimationStateListener inValue) { + _listener = inValue; + _listenerObject = NULL; +} + +void TrackEntry::setListener(AnimationStateListenerObject *inValue) { + _listener = dummyOnAnimationEventFunc; + _listenerObject = inValue; +} + +void TrackEntry::reset() { + _animation = NULL; + _previous = NULL; + _next = NULL; + _mixingFrom = NULL; + _mixingTo = NULL; + + setRendererObject(NULL); + + _timelineMode.clear(); + _timelineHoldMix.clear(); + _timelinesRotation.clear(); + + _listener = dummyOnAnimationEventFunc; + _listenerObject = NULL; +} + +float TrackEntry::getTrackComplete() { + float duration = _animationEnd - _animationStart; + if (duration != 0) { + if (_loop) return duration * (1 + (int) (_trackTime / duration));// Completion of next loop. + if (_trackTime < duration) return duration; // Before duration. + } + return _trackTime;// Next update. +} + +bool TrackEntry::wasApplied() { + return _nextTrackLast != -1; +} + +EventQueueEntry::EventQueueEntry(EventType eventType, TrackEntry *trackEntry, Event *event) : _type(eventType), + _entry(trackEntry), + _event(event) { +} + +EventQueue *EventQueue::newEventQueue(AnimationState &state) { + return new (__FILE__, __LINE__) EventQueue(state); +} + +EventQueueEntry EventQueue::newEventQueueEntry(EventType eventType, TrackEntry *entry, Event *event) { + return EventQueueEntry(eventType, entry, event); +} + +EventQueue::EventQueue(AnimationState &state) : _state(state), + _drainDisabled(false) { +} + +EventQueue::~EventQueue() { +} + +void EventQueue::start(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Start, entry)); + _state._animationsChanged = true; +} + +void EventQueue::interrupt(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Interrupt, entry)); +} + +void EventQueue::end(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_End, entry)); + _state._animationsChanged = true; +} + +void EventQueue::dispose(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Dispose, entry)); +} + +void EventQueue::complete(TrackEntry *entry) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Complete, entry)); +} + +void EventQueue::event(TrackEntry *entry, Event *event) { + _eventQueueEntries.add(newEventQueueEntry(EventType_Event, entry, event)); +} + +/// Raises all events in the queue and drains the queue. +void EventQueue::drain() { + if (_drainDisabled) { + return; + } + + _drainDisabled = true; + + AnimationState &state = _state; + + // Don't cache _eventQueueEntries.size() so callbacks can queue their own events (eg, call setAnimation in AnimationState_Complete). + for (size_t i = 0; i < _eventQueueEntries.size(); ++i) { + EventQueueEntry queueEntry = _eventQueueEntries[i]; + TrackEntry *trackEntry = queueEntry._entry; + + switch (queueEntry._type) { + case EventType_Start: + case EventType_Interrupt: + case EventType_Complete: + if (!trackEntry->_listenerObject) trackEntry->_listener(&state, queueEntry._type, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, NULL); + else + state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + break; + case EventType_End: + if (!trackEntry->_listenerObject) trackEntry->_listener(&state, queueEntry._type, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, NULL); + else + state._listenerObject->callback(&state, queueEntry._type, trackEntry, NULL); + /* Fall through. */ + case EventType_Dispose: + if (!trackEntry->_listenerObject) trackEntry->_listener(&state, EventType_Dispose, trackEntry, NULL); + else + trackEntry->_listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL); + if (!state._listenerObject) state._listener(&state, EventType_Dispose, trackEntry, NULL); + else + state._listenerObject->callback(&state, EventType_Dispose, trackEntry, NULL); + + if (!_state.getManualTrackEntryDisposal()) _state.disposeTrackEntry(trackEntry); + break; + case EventType_Event: + if (!trackEntry->_listenerObject) + trackEntry->_listener(&state, queueEntry._type, trackEntry, queueEntry._event); + else + trackEntry->_listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event); + if (!state._listenerObject) state._listener(&state, queueEntry._type, trackEntry, queueEntry._event); + else + state._listenerObject->callback(&state, queueEntry._type, trackEntry, queueEntry._event); + break; + } + } + _eventQueueEntries.clear(); + + _drainDisabled = false; +} + +AnimationState::AnimationState(AnimationStateData *data) : _data(data), + _queue(EventQueue::newEventQueue(*this)), + _animationsChanged(false), + _listener(dummyOnAnimationEventFunc), + _listenerObject(NULL), + _unkeyedState(0), + _timeScale(1), + _manualTrackEntryDisposal(false) { +} + +AnimationState::~AnimationState() { + for (size_t i = 0; i < _tracks.size(); i++) { + TrackEntry *entry = _tracks[i]; + if (entry) { + TrackEntry *from = entry->_mixingFrom; + while (from) { + TrackEntry *curr = from; + from = curr->_mixingFrom; + delete curr; + } + TrackEntry *next = entry->_next; + while (next) { + TrackEntry *curr = next; + next = curr->_next; + delete curr; + } + delete entry; + } + } + delete _queue; +} + +void AnimationState::update(float delta) { + delta *= _timeScale; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *currentP = _tracks[i]; + if (currentP == NULL) { + continue; + } + + TrackEntry ¤t = *currentP; + + current._animationLast = current._nextAnimationLast; + current._trackLast = current._nextTrackLast; + + float currentDelta = delta * current._timeScale; + + if (current._delay > 0) { + current._delay -= currentDelta; + if (current._delay > 0) { + continue; + } + currentDelta = -current._delay; + current._delay = 0; + } + + TrackEntry *next = current._next; + if (next != NULL) { + // When the next entry's delay is passed, change to the next entry, preserving leftover time. + float nextTime = current._trackLast - next->_delay; + if (nextTime >= 0) { + next->_delay = 0; + next->_trackTime += + current._timeScale == 0 ? 0 : (nextTime / current._timeScale + delta) * next->_timeScale; + current._trackTime += currentDelta; + setCurrent(i, next, true); + while (next->_mixingFrom != NULL) { + next->_mixTime += delta; + next = next->_mixingFrom; + } + continue; + } + } else if (current._trackLast >= current._trackEnd && current._mixingFrom == NULL) { + // clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. + _tracks[i] = NULL; + + _queue->end(currentP); + clearNext(currentP); + continue; + } + + if (current._mixingFrom != NULL && updateMixingFrom(currentP, delta)) { + // End mixing from entries once all have completed. + TrackEntry *from = current._mixingFrom; + current._mixingFrom = NULL; + if (from != NULL) from->_mixingTo = NULL; + while (from != NULL) { + _queue->end(from); + from = from->_mixingFrom; + } + } + + current._trackTime += currentDelta; + } + + _queue->drain(); +} + +bool AnimationState::apply(Skeleton &skeleton) { + if (_animationsChanged) { + animationsChanged(); + } + + bool applied = false; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *currentP = _tracks[i]; + if (currentP == NULL || currentP->_delay > 0) { + continue; + } + + TrackEntry ¤t = *currentP; + + applied = true; + MixBlend blend = i == 0 ? MixBlend_First : current._mixBlend; + + // apply mixing from entries first. + float alpha = current._alpha; + if (current._mixingFrom != NULL) { + alpha *= applyMixingFrom(currentP, skeleton, blend); + } else if (current._trackTime >= current._trackEnd && current._next == NULL) { + alpha = 0;// Set to setup pose the last time the entry will be applied. + } + bool attachments = alpha >= current._alphaAttachmentThreshold; + + + // apply current entry. + float animationLast = current._animationLast, animationTime = current.getAnimationTime(); + float applyTime = animationTime; + Vector *applyEvents = &_events; + if (current._reverse) { + applyTime = current._animation->getDuration() - applyTime; + applyEvents = NULL; + } + size_t timelineCount = current._animation->_timelines.size(); + Vector &timelines = current._animation->_timelines; + if ((i == 0 && alpha == 1) || blend == MixBlend_Add) { + if (i == 0) attachments = true; + for (size_t ii = 0; ii < timelineCount; ++ii) { + Timeline *timeline = timelines[ii]; + if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) + applyAttachmentTimeline(static_cast(timeline), skeleton, applyTime, blend, + attachments); + else + timeline->apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection_In); + } + } else { + Vector &timelineMode = current._timelineMode; + + bool shortestRotation = current._shortestRotation; + bool firstFrame = !shortestRotation && current._timelinesRotation.size() != timelines.size() << 1; + if (firstFrame) current._timelinesRotation.setSize(timelines.size() << 1, 0); + Vector &timelinesRotation = current._timelinesRotation; + + for (size_t ii = 0; ii < timelineCount; ++ii) { + Timeline *timeline = timelines[ii]; + assert(timeline); + + MixBlend timelineBlend = timelineMode[ii] == Subsequent ? blend : MixBlend_Setup; + + if (!shortestRotation && timeline->getRTTI().isExactly(RotateTimeline::rtti)) + applyRotateTimeline(static_cast(timeline), skeleton, applyTime, alpha, + timelineBlend, timelinesRotation, ii << 1, firstFrame); + else if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) + applyAttachmentTimeline(static_cast(timeline), skeleton, applyTime, + blend, attachments); + else + timeline->apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, + MixDirection_In); + } + } + + queueEvents(currentP, animationTime); + _events.clear(); + current._nextAnimationLast = animationTime; + current._nextTrackLast = current._trackTime; + } + + int setupState = _unkeyedState + Setup; + Vector &slots = skeleton.getSlots(); + for (int i = 0, n = (int) slots.size(); i < n; i++) { + Slot *slot = slots[i]; + if (slot->getAttachmentState() == setupState) { + const String &attachmentName = slot->getData().getAttachmentName(); + slot->setAttachment(attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot->getData().getIndex(), attachmentName)); + } + } + _unkeyedState += 2; + + _queue->drain(); + return applied; +} + +void AnimationState::clearTracks() { + bool oldDrainDisabled = _queue->_drainDisabled; + _queue->_drainDisabled = true; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) + clearTrack(i); + _tracks.clear(); + _queue->_drainDisabled = oldDrainDisabled; + _queue->drain(); +} + +void AnimationState::clearTrack(size_t trackIndex) { + if (trackIndex >= _tracks.size()) return; + + TrackEntry *current = _tracks[trackIndex]; + if (current == NULL) return; + + _queue->end(current); + + clearNext(current); + + TrackEntry *entry = current; + while (true) { + TrackEntry *from = entry->_mixingFrom; + if (from == NULL) break; + + _queue->end(from); + entry->_mixingFrom = NULL; + entry->_mixingTo = NULL; + entry = from; + } + + _tracks[current->_trackIndex] = NULL; + + _queue->drain(); +} + +TrackEntry *AnimationState::setAnimation(size_t trackIndex, const String &animationName, bool loop) { + Animation *animation = _data->_skeletonData->findAnimation(animationName); + assert(animation != NULL); + return setAnimation(trackIndex, animation, loop); +} + +TrackEntry *AnimationState::setAnimation(size_t trackIndex, Animation *animation, bool loop) { + assert(animation != NULL); + + bool interrupt = true; + TrackEntry *current = expandToIndex(trackIndex); + if (current != NULL) { + if (current->_nextTrackLast == -1) { + // Don't mix from an entry that was never applied. + _tracks[trackIndex] = current->_mixingFrom; + _queue->interrupt(current); + _queue->end(current); + clearNext(current); + current = current->_mixingFrom; + interrupt = false; + } else { + clearNext(current); + } + } + + TrackEntry *entry = newTrackEntry(trackIndex, animation, loop, current); + setCurrent(trackIndex, entry, interrupt); + _queue->drain(); + + return entry; +} + +TrackEntry *AnimationState::addAnimation(size_t trackIndex, const String &animationName, bool loop, float delay) { + Animation *animation = _data->_skeletonData->findAnimation(animationName); + assert(animation != NULL); + return addAnimation(trackIndex, animation, loop, delay); +} + +TrackEntry *AnimationState::addAnimation(size_t trackIndex, Animation *animation, bool loop, float delay) { + assert(animation != NULL); + + TrackEntry *last = expandToIndex(trackIndex); + if (last != NULL) { + while (last->_next != NULL) + last = last->_next; + } + + TrackEntry *entry = newTrackEntry(trackIndex, animation, loop, last); + + if (last == NULL) { + setCurrent(trackIndex, entry, true); + _queue->drain(); + if (delay < 0) delay = 0; + } else { + last->_next = entry; + entry->_previous = last; + if (delay <= 0) delay = MathUtil::max(delay + last->getTrackComplete() - entry->_mixDuration, 0.0f); + } + + entry->_delay = delay; + return entry; +} + +TrackEntry *AnimationState::setEmptyAnimation(size_t trackIndex, float mixDuration) { + TrackEntry *entry = setAnimation(trackIndex, AnimationState::getEmptyAnimation(), false); + entry->_mixDuration = mixDuration; + entry->_trackEnd = mixDuration; + return entry; +} + +TrackEntry *AnimationState::addEmptyAnimation(size_t trackIndex, float mixDuration, float delay) { + TrackEntry *entry = addAnimation(trackIndex, AnimationState::getEmptyAnimation(), false, delay); + if (delay <= 0) entry->_delay = MathUtil::max(entry->_delay + entry->_mixDuration - mixDuration, 0.0f); + entry->_mixDuration = mixDuration; + entry->_trackEnd = mixDuration; + return entry; +} + +void AnimationState::setEmptyAnimations(float mixDuration) { + bool oldDrainDisabled = _queue->_drainDisabled; + _queue->_drainDisabled = true; + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *current = _tracks[i]; + if (current != NULL) { + setEmptyAnimation(i, mixDuration); + } + } + _queue->_drainDisabled = oldDrainDisabled; + _queue->drain(); +} + +TrackEntry *AnimationState::getCurrent(size_t trackIndex) { + return trackIndex >= _tracks.size() ? NULL : _tracks[trackIndex]; +} + +AnimationStateData *AnimationState::getData() { + return _data; +} + +Vector &AnimationState::getTracks() { + return _tracks; +} + +float AnimationState::getTimeScale() { + return _timeScale; +} + +void AnimationState::setTimeScale(float inValue) { + _timeScale = inValue; +} + +void AnimationState::setListener(AnimationStateListener inValue) { + _listener = inValue; + _listenerObject = NULL; +} + +void AnimationState::setListener(AnimationStateListenerObject *inValue) { + _listener = dummyOnAnimationEventFunc; + _listenerObject = inValue; +} + +void AnimationState::disableQueue() { + _queue->_drainDisabled = true; +} + +void AnimationState::enableQueue() { + _queue->_drainDisabled = false; +} + +void AnimationState::setManualTrackEntryDisposal(bool inValue) { + _manualTrackEntryDisposal = inValue; +} + +bool AnimationState::getManualTrackEntryDisposal() { + return _manualTrackEntryDisposal; +} + +void AnimationState::disposeTrackEntry(TrackEntry *entry) { + entry->reset(); + _trackEntryPool.free(entry); +} + +Animation *AnimationState::getEmptyAnimation() { + static Vector timelines; + static Animation ret(String(""), timelines, 0); + return &ret; +} + +void AnimationState::applyAttachmentTimeline(AttachmentTimeline *attachmentTimeline, Skeleton &skeleton, float time, + MixBlend blend, bool attachments) { + Slot *slot = skeleton.getSlots()[attachmentTimeline->getSlotIndex()]; + if (!slot->getBone().isActive()) return; + + Vector &frames = attachmentTimeline->getFrames(); + if (time < frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) + setAttachment(skeleton, *slot, slot->getData().getAttachmentName(), attachments); + } else { + setAttachment(skeleton, *slot, attachmentTimeline->getAttachmentNames()[Animation::search(frames, time)], + attachments); + } + + /* If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.*/ + if (slot->getAttachmentState() <= _unkeyedState) slot->setAttachmentState(_unkeyedState + Setup); +} + + +void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleton &skeleton, float time, float alpha, + MixBlend blend, Vector &timelinesRotation, size_t i, bool firstFrame) { + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) { + rotateTimeline->apply(skeleton, 0, time, NULL, 1, blend, MixDirection_In); + return; + } + + Bone *bone = skeleton._bones[rotateTimeline->_boneIndex]; + if (!bone->isActive()) return; + Vector &frames = rotateTimeline->_frames; + float r1, r2; + if (time < frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_rotation = bone->_data._rotation; + default: + return; + case MixBlend_First: + r1 = bone->_rotation; + r2 = bone->_data._rotation; + } + } else { + r1 = blend == MixBlend_Setup ? bone->_data._rotation : bone->_rotation; + r2 = bone->_data._rotation + rotateTimeline->getCurveValue(time); + } + + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. + float total, diff = r2 - r1; + diff -= MathUtil::ceil(diff / 360 - 0.5) * 360; + if (diff == 0) { + total = timelinesRotation[i]; + } else { + float lastTotal, lastDiff; + if (firstFrame) { + lastTotal = 0; + lastDiff = diff; + } else { + lastTotal = timelinesRotation[i]; + lastDiff = timelinesRotation[i + 1]; + } + float loops = lastTotal - MathUtil::fmod(lastTotal, 360.f); + total = diff + loops; + bool current = diff >= 0, dir = lastTotal >= 0; + if (MathUtil::abs(lastDiff) <= 90 && MathUtil::sign(lastDiff) != MathUtil::sign(diff)) { + if (MathUtil::abs(lastTotal - loops) > 180) { + total += 360.f * MathUtil::sign(lastTotal); + dir = current; + } else if (loops != 0) + total -= 360.f * MathUtil::sign(lastTotal); + else + dir = current; + } + if (dir != current) { + total += 360 * MathUtil::sign(lastTotal); + } + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + bone->_rotation = r1 + total * alpha; +} + +bool AnimationState::updateMixingFrom(TrackEntry *to, float delta) { + TrackEntry *from = to->_mixingFrom; + if (from == NULL) { + return true; + } + + bool finished = updateMixingFrom(from, delta); + + from->_animationLast = from->_nextAnimationLast; + from->_trackLast = from->_nextTrackLast; + + // The from entry was applied at least once and the mix is complete. + if (to->_nextTrackLast != -1 && to->_mixTime >= to->_mixDuration) { + // Mixing is complete for all entries before the from entry or the mix is instantaneous. + if (from->_totalAlpha == 0 || to->_mixDuration == 0) { + to->_mixingFrom = from->_mixingFrom; + if (from->_mixingFrom) from->_mixingFrom->_mixingTo = to; + to->_interruptAlpha = from->_interruptAlpha; + _queue->end(from); + } + return finished; + } + + from->_trackTime += delta * from->_timeScale; + to->_mixTime += delta; + + return false; +} + +float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBlend blend) { + TrackEntry *from = to->_mixingFrom; + if (from->_mixingFrom != NULL) applyMixingFrom(from, skeleton, blend); + + float mix; + if (to->_mixDuration == 0) { + // Single frame mix to undo mixingFrom changes. + mix = 1; + if (blend == MixBlend_First) blend = MixBlend_Setup; + } else { + mix = to->_mixTime / to->_mixDuration; + if (mix > 1) { + mix = 1; + } + if (blend != MixBlend_First) blend = from->_mixBlend; + } + + bool attachments = mix < from->_mixAttachmentThreshold, drawOrder = mix < from->_mixDrawOrderThreshold; + Vector &timelines = from->_animation->_timelines; + size_t timelineCount = timelines.size(); + float alphaHold = from->_alpha * to->_interruptAlpha, alphaMix = alphaHold * (1 - mix); + float animationLast = from->_animationLast, animationTime = from->getAnimationTime(); + float applyTime = animationTime; + Vector *events = NULL; + if (from->_reverse) { + applyTime = from->_animation->_duration - applyTime; + } else { + if (mix < from->_eventThreshold) events = &_events; + } + + if (blend == MixBlend_Add) { + for (size_t i = 0; i < timelineCount; i++) + timelines[i]->apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection_Out); + } else { + Vector &timelineMode = from->_timelineMode; + Vector &timelineHoldMix = from->_timelineHoldMix; + + bool shortestRotation = from->_shortestRotation; + bool firstFrame = !shortestRotation && from->_timelinesRotation.size() != timelines.size() << 1; + if (firstFrame) from->_timelinesRotation.setSize(timelines.size() << 1, 0); + + Vector &timelinesRotation = from->_timelinesRotation; + + from->_totalAlpha = 0; + for (size_t i = 0; i < timelineCount; i++) { + Timeline *timeline = timelines[i]; + MixDirection direction = MixDirection_Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i]) { + case Subsequent: + if (!drawOrder && (timeline->getRTTI().isExactly(DrawOrderTimeline::rtti))) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case First: + timelineBlend = MixBlend_Setup; + alpha = alphaMix; + break; + case HoldSubsequent: + timelineBlend = blend; + alpha = alphaHold; + break; + case HoldFirst: + timelineBlend = MixBlend_Setup; + alpha = alphaHold; + break; + default: + timelineBlend = MixBlend_Setup; + TrackEntry *holdMix = timelineHoldMix[i]; + alpha = alphaHold * MathUtil::max(0.0f, 1.0f - holdMix->_mixTime / holdMix->_mixDuration); + break; + } + from->_totalAlpha += alpha; + if (!shortestRotation && (timeline->getRTTI().isExactly(RotateTimeline::rtti))) { + applyRotateTimeline((RotateTimeline *) timeline, skeleton, applyTime, alpha, timelineBlend, + timelinesRotation, i << 1, firstFrame); + } else if (timeline->getRTTI().isExactly(AttachmentTimeline::rtti)) { + applyAttachmentTimeline(static_cast(timeline), skeleton, applyTime, timelineBlend, + attachments && alpha >= from->_alphaAttachmentThreshold); + } else { + if (drawOrder && timeline->getRTTI().isExactly(DrawOrderTimeline::rtti) && + timelineBlend == MixBlend_Setup) + direction = MixDirection_In; + timeline->apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); + } + } + } + + if (to->_mixDuration > 0) { + queueEvents(from, animationTime); + } + + _events.clear(); + from->_nextAnimationLast = animationTime; + from->_nextTrackLast = from->_trackTime; + + return mix; +} + +void AnimationState::setAttachment(Skeleton &skeleton, Slot &slot, const String &attachmentName, bool attachments) { + slot.setAttachment( + attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot.getData().getIndex(), attachmentName)); + if (attachments) slot.setAttachmentState(_unkeyedState + Current); +} + +void AnimationState::queueEvents(TrackEntry *entry, float animationTime) { + float animationStart = entry->_animationStart, animationEnd = entry->_animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = duration != 0 ? MathUtil::fmod(entry->_trackLast, duration) : MathUtil::quietNan(); + + // Queue events before complete. + size_t i = 0, n = _events.size(); + for (; i < n; ++i) { + Event *e = _events[i]; + if (e->_time < trackLastWrapped) break; + if (e->_time > animationEnd) continue;// Discard events outside animation start/end. + _queue->event(entry, e); + } + + // Queue complete if completed a loop iteration or the animation. + bool complete = false; + if (entry->_loop) { + if (duration == 0) + complete = true; + else { + int cycles = (int) (entry->_trackTime / duration); + complete = cycles > 0 && cycles > (int) (entry->_trackLast / duration); + } + } else { + complete = animationTime >= animationEnd && entry->_animationLast < animationEnd; + } + if (complete) _queue->complete(entry); + + // Queue events after complete. + for (; i < n; ++i) { + Event *e = _events[i]; + if (e->_time < animationStart) continue;// Discard events outside animation start/end. + _queue->event(entry, e); + } +} + +void AnimationState::setCurrent(size_t index, TrackEntry *current, bool interrupt) { + TrackEntry *from = expandToIndex(index); + _tracks[index] = current; + current->_previous = NULL; + + if (from != NULL) { + if (interrupt) _queue->interrupt(from); + + current->_mixingFrom = from; + from->_mixingTo = current; + current->_mixTime = 0; + + // Store interrupted mix percentage. + if (from->_mixingFrom != NULL && from->_mixDuration > 0) { + current->_interruptAlpha *= MathUtil::min(1.0f, from->_mixTime / from->_mixDuration); + } + + from->_timelinesRotation.clear();// Reset rotation for mixing out, in case entry was mixed in. + } + + _queue->start(current);// triggers animationsChanged +} + +TrackEntry *AnimationState::expandToIndex(size_t index) { + if (index < _tracks.size()) return _tracks[index]; + while (index >= _tracks.size()) + _tracks.add(NULL); + return NULL; +} + +TrackEntry *AnimationState::newTrackEntry(size_t trackIndex, Animation *animation, bool loop, TrackEntry *last) { + TrackEntry *entryP = _trackEntryPool.obtain();// Pooling + TrackEntry &entry = *entryP; + + entry._trackIndex = (int) trackIndex; + entry._animation = animation; + entry._loop = loop; + entry._holdPrevious = 0; + + entry._reverse = false; + entry._shortestRotation = false; + + entry._eventThreshold = 0; + entry._alphaAttachmentThreshold = 0; + entry._mixAttachmentThreshold = 0; + entry._mixDrawOrderThreshold = 0; + + entry._animationStart = 0; + entry._animationEnd = animation->getDuration(); + entry._animationLast = -1; + entry._nextAnimationLast = -1; + + entry._delay = 0; + entry._trackTime = 0; + entry._trackLast = -1; + entry._nextTrackLast = -1;// nextTrackLast == -1 signifies a TrackEntry that wasn't applied yet. + entry._trackEnd = FLT_MAX;// loop ? float.MaxValue : animation.Duration; + entry._timeScale = 1; + + entry._alpha = 1; + entry._mixTime = 0; + entry._mixDuration = (last == NULL) ? 0 : _data->getMix(last->_animation, animation); + entry._interruptAlpha = 1; + entry._totalAlpha = 0; + entry._mixBlend = MixBlend_Replace; + + return entryP; +} + +void AnimationState::clearNext(TrackEntry *entry) { + TrackEntry *next = entry->_next; + while (next != NULL) { + _queue->dispose(next); + next = next->_next; + } + entry->_next = NULL; +} + +void AnimationState::animationsChanged() { + _animationsChanged = false; + + _propertyIDs.clear(); + + for (size_t i = 0, n = _tracks.size(); i < n; ++i) { + TrackEntry *entry = _tracks[i]; + if (!entry) continue; + + while (entry->_mixingFrom != NULL) + entry = entry->_mixingFrom; + + do { + if (entry->_mixingTo == NULL || entry->_mixBlend != MixBlend_Add) computeHold(entry); + entry = entry->_mixingTo; + } while (entry != NULL); + } +} + +void AnimationState::computeHold(TrackEntry *entry) { + TrackEntry *to = entry->_mixingTo; + Vector &timelines = entry->_animation->_timelines; + size_t timelinesCount = timelines.size(); + Vector &timelineMode = entry->_timelineMode; + timelineMode.setSize(timelinesCount, 0); + Vector &timelineHoldMix = entry->_timelineHoldMix; + timelineHoldMix.setSize(timelinesCount, 0); + + if (to != NULL && to->_holdPrevious) { + for (size_t i = 0; i < timelinesCount; i++) { + timelineMode[i] = _propertyIDs.addAll(timelines[i]->getPropertyIds(), true) ? HoldFirst : HoldSubsequent; + } + return; + } + + // outer: + size_t i = 0; +continue_outer: + for (; i < timelinesCount; ++i) { + Timeline *timeline = timelines[i]; + Vector &ids = timeline->getPropertyIds(); + if (!_propertyIDs.addAll(ids, true)) { + timelineMode[i] = Subsequent; + } else { + if (to == NULL || timeline->getRTTI().isExactly(AttachmentTimeline::rtti) || + timeline->getRTTI().isExactly(DrawOrderTimeline::rtti) || + timeline->getRTTI().isExactly(EventTimeline::rtti) || !to->_animation->hasTimeline(ids)) { + timelineMode[i] = First; + } else { + for (TrackEntry *next = to->_mixingTo; next != NULL; next = next->_mixingTo) { + if (next->_animation->hasTimeline(ids)) continue; + if (next->_mixDuration > 0) { + timelineMode[i] = HoldMix; + timelineHoldMix[i] = next; + i++; + goto continue_outer;// continue outer; + } + break; + } + timelineMode[i] = HoldFirst; + } + } + } +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AnimationStateData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AnimationStateData.cpp new file mode 100644 index 0000000..9ba25ca --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AnimationStateData.cpp @@ -0,0 +1,86 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include + +using namespace spine; + +AnimationStateData::AnimationStateData(SkeletonData *skeletonData) : _skeletonData(skeletonData), _defaultMix(0) { +} + +void AnimationStateData::setMix(const String &fromName, const String &toName, float duration) { + Animation *from = _skeletonData->findAnimation(fromName); + Animation *to = _skeletonData->findAnimation(toName); + + setMix(from, to, duration); +} + +void AnimationStateData::setMix(Animation *from, Animation *to, float duration) { + assert(from != NULL); + assert(to != NULL); + + AnimationPair key(from, to); + _animationToMixTime.put(key, duration); +} + +float AnimationStateData::getMix(Animation *from, Animation *to) { + assert(from != NULL); + assert(to != NULL); + + AnimationPair key(from, to); + + if (_animationToMixTime.containsKey(key)) return _animationToMixTime[key]; + return _defaultMix; +} + +SkeletonData *AnimationStateData::getSkeletonData() { + return _skeletonData; +} + +float AnimationStateData::getDefaultMix() { + return _defaultMix; +} + +void AnimationStateData::setDefaultMix(float inValue) { + _defaultMix = inValue; +} + +void AnimationStateData::clear() { + _defaultMix = 0; + _animationToMixTime.clear(); +} + +AnimationStateData::AnimationPair::AnimationPair(Animation *a1, Animation *a2) : _a1(a1), _a2(a2) { +} + +bool AnimationStateData::AnimationPair::operator==(const AnimationPair &other) const { + return _a1->_name == other._a1->_name && _a2->_name == other._a2->_name; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Atlas.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Atlas.cpp new file mode 100644 index 0000000..9258fa0 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Atlas.cpp @@ -0,0 +1,350 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include + +#include + +using namespace spine; + +Atlas::Atlas(const String &path, TextureLoader *textureLoader, bool createTexture) : _textureLoader(textureLoader) { + int dirLength; + char *dir; + int length; + const char *data; + + /* Get directory from atlas path. */ + const char *lastForwardSlash = strrchr(path.buffer(), '/'); + const char *lastBackwardSlash = strrchr(path.buffer(), '\\'); + const char *lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash; + if (lastSlash == path) lastSlash++; /* Never drop starting slash. */ + dirLength = (int) (lastSlash ? lastSlash - path.buffer() : 0); + dir = SpineExtension::calloc(dirLength + 1, __FILE__, __LINE__); + memcpy(dir, path.buffer(), dirLength); + dir[dirLength] = '\0'; + + data = SpineExtension::readFile(path, &length); + if (data) { + load(data, length, dir, createTexture); + } + + SpineExtension::free(data, __FILE__, __LINE__); + SpineExtension::free(dir, __FILE__, __LINE__); +} + +Atlas::Atlas(const char *data, int length, const char *dir, TextureLoader *textureLoader, bool createTexture) + : _textureLoader( + textureLoader) { + load(data, length, dir, createTexture); +} + +Atlas::~Atlas() { + if (_textureLoader) { + for (size_t i = 0, n = _pages.size(); i < n; ++i) { + _textureLoader->unload(_pages[i]->texture); + } + } + ContainerUtil::cleanUpVectorOfPointers(_pages); + ContainerUtil::cleanUpVectorOfPointers(_regions); +} + +void Atlas::flipV() { + for (size_t i = 0, n = _regions.size(); i < n; ++i) { + AtlasRegion *regionP = _regions[i]; + AtlasRegion ®ion = *regionP; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } +} + +AtlasRegion *Atlas::findRegion(const String &name) { + for (size_t i = 0, n = _regions.size(); i < n; ++i) + if (_regions[i]->name == name) return _regions[i]; + return NULL; +} + +Vector &Atlas::getPages() { + return _pages; +} + +Vector &Atlas::getRegions() { + return _regions; +} + +struct SimpleString { + char *start; + char *end; + int length; + + SimpleString trim() { + while (isspace((unsigned char) *start) && start < end) + start++; + if (start == end) { + length = (int) (end - start); + return *this; + } + end--; + while (((unsigned char) *end == '\r') && end >= start) + end--; + end++; + length = (int) (end - start); + return *this; + } + + int indexOf(char needle) { + char *c = start; + while (c < end) { + if (*c == needle) return (int) (c - start); + c++; + } + return -1; + } + + int indexOf(char needle, int at) { + char *c = start + at; + while (c < end) { + if (*c == needle) return (int) (c - start); + c++; + } + return -1; + } + + SimpleString substr(int s, int e) { + e = s + e; + SimpleString result; + result.start = start + s; + result.end = start + e; + result.length = e - s; + return result; + } + + SimpleString substr(int s) { + SimpleString result; + result.start = start + s; + result.end = end; + result.length = (int) (result.end - result.start); + return result; + } + + bool equals(const char *str) { + int otherLen = (int) strlen(str); + if (length != otherLen) return false; + for (int i = 0; i < length; i++) { + if (start[i] != str[i]) return false; + } + return true; + } + + char *copy() { + char *string = SpineExtension::calloc(length + 1, __FILE__, __LINE__); + memcpy(string, start, length); + string[length] = '\0'; + return string; + } + + int toInt() { + return (int) strtol(start, &end, 10); + } +}; + +struct AtlasInput { + const char *start; + const char *end; + char *index; + int length; + SimpleString line; + + AtlasInput(const char *data, int length) : start(data), end(data + length), index((char *) data), length(length) {} + + SimpleString *readLine() { + if (index >= end) return 0; + line.start = index; + while (index < end && *index != '\n') + index++; + line.end = index; + if (index != end) index++; + line = line.trim(); + line.length = (int) (end - start); + return &line; + } + + static int readEntry(SimpleString entry[5], SimpleString *line) { + if (line == NULL) return 0; + line->trim(); + if (line->length == 0) return 0; + + int colon = line->indexOf(':'); + if (colon == -1) return 0; + entry[0] = line->substr(0, colon).trim(); + for (int i = 1, lastMatch = colon + 1;; i++) { + int comma = line->indexOf(',', lastMatch); + if (comma == -1) { + entry[i] = line->substr(lastMatch).trim(); + return i; + } + entry[i] = line->substr(lastMatch, comma - lastMatch).trim(); + lastMatch = comma + 1; + if (i == 4) return 4; + } + } +}; + +int indexOf(const char **array, int count, SimpleString *str) { + for (int i = 0; i < count; i++) + if (str->equals(array[i])) return i; + return 0; +} + +void Atlas::load(const char *begin, int length, const char *dir, bool createTexture) { + static const char *formatNames[] = {"", "Alpha", "Intensity", "LuminanceAlpha", "RGB565", "RGBA4444", "RGB888", + "RGBA8888"}; + static const char *textureFilterNames[] = {"", "Nearest", "Linear", "MipMap", "MipMapNearestNearest", + "MipMapLinearNearest", + "MipMapNearestLinear", "MipMapLinearLinear"}; + + int dirLength = (int) strlen(dir); + int needsSlash = dirLength > 0 && dir[dirLength - 1] != '/' && dir[dirLength - 1] != '\\'; + AtlasInput reader(begin, length); + SimpleString entry[5]; + AtlasPage *page = NULL; + + SimpleString *line = reader.readLine(); + while (line != NULL && line->length == 0) + line = reader.readLine(); + + while (true) { + if (line == NULL || line->length == 0) break; + if (reader.readEntry(entry, line) == 0) break; + line = reader.readLine(); + } + + while (true) { + if (line == NULL) break; + if (line->trim().length == 0) { + page = NULL; + line = reader.readLine(); + } else if (page == NULL) { + char *name = line->copy(); + char *path = SpineExtension::calloc(dirLength + needsSlash + strlen(name) + 1, __FILE__, __LINE__); + memcpy(path, dir, dirLength); + if (needsSlash) path[dirLength] = '/'; + strcpy(path + dirLength + needsSlash, name); + page = new (__FILE__, __LINE__) AtlasPage(String(name, true)); + + while (true) { + line = reader.readLine(); + if (reader.readEntry(entry, line) == 0) break; + if (entry[0].equals("size")) { + page->width = entry[1].toInt(); + page->height = entry[2].toInt(); + } else if (entry[0].equals("format")) { + page->format = (Format) indexOf(formatNames, 8, &entry[1]); + } else if (entry[0].equals("filter")) { + page->minFilter = (TEXTURE_FILTER_ENUM) indexOf(textureFilterNames, 8, &entry[1]); + page->magFilter = (TEXTURE_FILTER_ENUM) indexOf(textureFilterNames, 8, &entry[2]); + } else if (entry[0].equals("repeat")) { + page->uWrap = TextureWrap_ClampToEdge; + page->vWrap = TextureWrap_ClampToEdge; + if (entry[1].indexOf('x') != -1) page->uWrap = TextureWrap_Repeat; + if (entry[1].indexOf('y') != -1) page->vWrap = TextureWrap_Repeat; + } else if (entry[0].equals("pma")) { + page->pma = entry[1].equals("true"); + } + } + + page->index = (int) _pages.size(); + if (createTexture && _textureLoader) _textureLoader->load(*page, String(path)); + page->texturePath = String(path, true); + _pages.add(page); + } else { + AtlasRegion *region = new (__FILE__, __LINE__) AtlasRegion(); + region->page = page; + region->rendererObject = page->texture; + region->name = String(line->copy(), true); + while (true) { + line = reader.readLine(); + int count = reader.readEntry(entry, line); + if (count == 0) break; + if (entry[0].equals("xy")) { + region->x = entry[1].toInt(); + region->y = entry[2].toInt(); + } else if (entry[0].equals("size")) { + region->width = entry[1].toInt(); + region->height = entry[2].toInt(); + } else if (entry[0].equals("bounds")) { + region->x = entry[1].toInt(); + region->y = entry[2].toInt(); + region->width = entry[3].toInt(); + region->height = entry[4].toInt(); + } else if (entry[0].equals("offset")) { + region->offsetX = entry[1].toInt(); + region->offsetY = entry[2].toInt(); + } else if (entry[0].equals("orig")) { + region->originalWidth = entry[1].toInt(); + region->originalHeight = entry[2].toInt(); + } else if (entry[0].equals("offsets")) { + region->offsetX = entry[1].toInt(); + region->offsetY = entry[2].toInt(); + region->originalWidth = entry[3].toInt(); + region->originalHeight = entry[4].toInt(); + } else if (entry[0].equals("rotate")) { + if (entry[1].equals("true")) { + region->degrees = 90; + } else if (!entry[1].equals("false")) { + region->degrees = entry[1].toInt(); + } + } else if (entry[0].equals("index")) { + region->index = entry[1].toInt(); + } else { + region->names.add(String(entry[0].copy())); + for (int i = 0; i < count; i++) { + region->values.add(entry[i + 1].toInt()); + } + } + } + if (region->originalWidth == 0 && region->originalHeight == 0) { + region->originalWidth = region->width; + region->originalHeight = region->height; + } + + region->u = (float) region->x / page->width; + region->v = (float) region->y / page->height; + if (region->degrees == 90) { + region->u2 = (float) (region->x + region->height) / page->width; + region->v2 = (float) (region->y + region->width) / page->height; + } else { + region->u2 = (float) (region->x + region->width) / page->width; + region->v2 = (float) (region->y + region->height) / page->height; + } + _regions.add(region); + } + } +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AtlasAttachmentLoader.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AtlasAttachmentLoader.cpp new file mode 100644 index 0000000..ff3b009 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AtlasAttachmentLoader.cpp @@ -0,0 +1,112 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace spine { + RTTI_IMPL(AtlasAttachmentLoader, AttachmentLoader) + + AtlasAttachmentLoader::AtlasAttachmentLoader(Atlas *atlas) : AttachmentLoader(), _atlas(atlas) { + } + + bool loadSequence(Atlas *atlas, const String &basePath, Sequence *sequence) { + Vector ®ions = sequence->getRegions(); + for (int i = 0, n = (int) regions.size(); i < n; i++) { + String path = sequence->getPath(basePath, i); + regions[i] = atlas->findRegion(path); + if (!regions[i]) return false; + } + return true; + } + + RegionAttachment *AtlasAttachmentLoader::newRegionAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) { + SP_UNUSED(skin); + RegionAttachment *attachment = new (__FILE__, __LINE__) RegionAttachment(name); + if (sequence) { + if (!loadSequence(_atlas, path, sequence)) return NULL; + } else { + AtlasRegion *region = findRegion(path); + if (!region) return NULL; + attachment->setRegion(region); + } + return attachment; + } + + MeshAttachment *AtlasAttachmentLoader::newMeshAttachment(Skin &skin, const String &name, const String &path, Sequence *sequence) { + SP_UNUSED(skin); + MeshAttachment *attachment = new (__FILE__, __LINE__) MeshAttachment(name); + + if (sequence) { + if (!loadSequence(_atlas, path, sequence)) return NULL; + } else { + AtlasRegion *region = findRegion(path); + if (!region) return NULL; + attachment->setRegion(region); + } + return attachment; + } + + BoundingBoxAttachment *AtlasAttachmentLoader::newBoundingBoxAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) BoundingBoxAttachment(name); + } + + PathAttachment *AtlasAttachmentLoader::newPathAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) PathAttachment(name); + } + + PointAttachment *AtlasAttachmentLoader::newPointAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) PointAttachment(name); + } + + ClippingAttachment *AtlasAttachmentLoader::newClippingAttachment(Skin &skin, const String &name) { + SP_UNUSED(skin); + return new (__FILE__, __LINE__) ClippingAttachment(name); + } + + void AtlasAttachmentLoader::configureAttachment(Attachment *attachment) { + SP_UNUSED(attachment); + } + + AtlasRegion *AtlasAttachmentLoader::findRegion(const String &name) { + return _atlas->findRegion(name); + } + +}// namespace spine diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Attachment.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Attachment.cpp new file mode 100644 index 0000000..e46122a --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Attachment.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(Attachment) + +Attachment::Attachment(const String &name) : _name(name), _refCount(0) { + assert(_name.length() > 0); +} + +Attachment::~Attachment() { +} + +const String &Attachment::getName() const { + return _name; +} + +int Attachment::getRefCount() { + return _refCount; +} + +void Attachment::reference() { + _refCount++; +} + +void Attachment::dereference() { + _refCount--; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AttachmentLoader.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AttachmentLoader.cpp new file mode 100644 index 0000000..87f1007 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AttachmentLoader.cpp @@ -0,0 +1,48 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(AttachmentLoader) + +AttachmentLoader::AttachmentLoader() { +} + +AttachmentLoader::~AttachmentLoader() { +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AttachmentTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AttachmentTimeline.cpp new file mode 100644 index 0000000..f439cd3 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/AttachmentTimeline.cpp @@ -0,0 +1,100 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(AttachmentTimeline, Timeline) + +AttachmentTimeline::AttachmentTimeline(size_t frameCount, int slotIndex) : Timeline(frameCount, 1), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Attachment << 32) | slotIndex}; + setPropertyIds(ids, 1); + + _attachmentNames.ensureCapacity(frameCount); + for (size_t i = 0; i < frameCount; ++i) { + _attachmentNames.add(String()); + } +} + +AttachmentTimeline::~AttachmentTimeline() {} + +void AttachmentTimeline::setAttachment(Skeleton &skeleton, Slot &slot, String *attachmentName) { + slot.setAttachment(attachmentName == NULL || attachmentName->isEmpty() ? NULL : skeleton.getAttachment(_slotIndex, *attachmentName)); +} + +void AttachmentTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(alpha); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) setAttachment(skeleton, *slot, &slot->_data._attachmentName); + return; + } + + if (time < _frames[0]) { + // Time is before first frame. + if (blend == MixBlend_Setup || blend == MixBlend_First) { + setAttachment(skeleton, *slot, &slot->_data._attachmentName); + } + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) + setAttachment(skeleton, *slot, &slot->_data._attachmentName); + return; + } + + setAttachment(skeleton, *slot, &_attachmentNames[Animation::search(_frames, time)]); +} + +void AttachmentTimeline::setFrame(int frame, float time, const String &attachmentName) { + _frames[frame] = time; + _attachmentNames[frame] = attachmentName; +} + +Vector &AttachmentTimeline::getAttachmentNames() { + return _attachmentNames; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Bone.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Bone.cpp new file mode 100644 index 0000000..9a78078 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Bone.cpp @@ -0,0 +1,595 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(Bone, Updatable) + +bool Bone::yDown = false; + +void Bone::setYDown(bool inValue) { + yDown = inValue; +} + +bool Bone::isYDown() { + return yDown; +} + +Bone::Bone(BoneData &data, Skeleton &skeleton, Bone *parent) : Updatable(), + _data(data), + _skeleton(skeleton), + _parent(parent), + _x(0), + _y(0), + _rotation(0), + _scaleX(0), + _scaleY(0), + _shearX(0), + _shearY(0), + _ax(0), + _ay(0), + _arotation(0), + _ascaleX(0), + _ascaleY(0), + _ashearX(0), + _ashearY(0), + _a(1), + _b(0), + _worldX(0), + _c(0), + _d(1), + _worldY(0), + _sorted(false), + _active(false), + _inherit(Inherit_Normal) { + setToSetupPose(); +} + +void Bone::update(Physics) { + updateWorldTransform(_ax, _ay, _arotation, _ascaleX, _ascaleY, _ashearX, _ashearY); +} + +void Bone::updateWorldTransform() { + updateWorldTransform(_x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY); +} + +void Bone::updateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { + float pa, pb, pc, pd; + Bone *parent = _parent; + + _ax = x; + _ay = y; + _arotation = rotation; + _ascaleX = scaleX; + _ascaleY = scaleY; + _ashearX = shearX; + _ashearY = shearY; + + if (!parent) { /* Root bone. */ + Skeleton &skeleton = this->_skeleton; + float sx = skeleton.getScaleX(); + float sy = skeleton.getScaleY(); + float rx = (rotation + shearX) * MathUtil::Deg_Rad; + float ry = (rotation + 90 + shearY) * MathUtil::Deg_Rad; + _a = MathUtil::cos(rx) * scaleX * sx; + _b = MathUtil::cos(ry) * scaleY * sx; + _c = MathUtil::sin(rx) * scaleX * sy; + _d = MathUtil::sin(ry) * scaleY * sy; + _worldX = x * sx + _skeleton.getX(); + _worldY = y * sy + _skeleton.getY(); + return; + } + + pa = parent->_a; + pb = parent->_b; + pc = parent->_c; + pd = parent->_d; + + _worldX = pa * x + pb * y + parent->_worldX; + _worldY = pc * x + pd * y + parent->_worldY; + + switch (_inherit) { + case Inherit_Normal: { + float rx = (rotation + shearX) * MathUtil::Deg_Rad; + float ry = (rotation + 90 + shearY) * MathUtil::Deg_Rad; + float la = MathUtil::cos(rx) * scaleX; + float lb = MathUtil::cos(ry) * scaleY; + float lc = MathUtil::sin(rx) * scaleX; + float ld = MathUtil::sin(ry) * scaleY; + _a = pa * la + pb * lc; + _b = pa * lb + pb * ld; + _c = pc * la + pd * lc; + _d = pc * lb + pd * ld; + return; + } + case Inherit_OnlyTranslation: { + float rx = (rotation + shearX) * MathUtil::Deg_Rad; + float ry = (rotation + 90 + shearY) * MathUtil::Deg_Rad; + _a = MathUtil::cos(rx) * scaleX; + _b = MathUtil::cos(ry) * scaleY; + _c = MathUtil::sin(rx) * scaleX; + _d = MathUtil::sin(ry) * scaleY; + break; + } + case Inherit_NoRotationOrReflection: { + float s = pa * pa + pc * pc; + float prx; + if (s > 0.0001f) { + s = MathUtil::abs(pa * pd - pb * pc) / s; + pa /= _skeleton.getScaleX(); + pc /= _skeleton.getScaleY(); + pb = pc * s; + pd = pa * s; + prx = MathUtil::atan2Deg(pc, pa); + } else { + pa = 0; + pc = 0; + prx = 90 - MathUtil::atan2Deg(pd, pb); + } + float rx = (rotation + shearX - prx) * MathUtil::Deg_Rad; + float ry = (rotation + shearY - prx + 90) * MathUtil::Deg_Rad; + float la = MathUtil::cos(rx) * scaleX; + float lb = MathUtil::cos(ry) * scaleY; + float lc = MathUtil::sin(rx) * scaleX; + float ld = MathUtil::sin(ry) * scaleY; + _a = pa * la - pb * lc; + _b = pa * lb - pb * ld; + _c = pc * la + pd * lc; + _d = pc * lb + pd * ld; + break; + } + case Inherit_NoScale: + case Inherit_NoScaleOrReflection: { + rotation *= MathUtil::Deg_Rad; + float cosine = MathUtil::cos(rotation); + float sine = MathUtil::sin(rotation); + float za = (pa * cosine + pb * sine) / _skeleton.getScaleX(); + float zc = (pc * cosine + pd * sine) / _skeleton.getScaleY(); + float s = MathUtil::sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = MathUtil::sqrt(za * za + zc * zc); + if (_inherit == Inherit_NoScale && + (pa * pd - pb * pc < 0) != (_skeleton.getScaleX() < 0 != _skeleton.getScaleY() < 0)) + s = -s; + rotation = MathUtil::Pi / 2 + MathUtil::atan2(zc, za); + float zb = MathUtil::cos(rotation) * s; + float zd = MathUtil::sin(rotation) * s; + shearX *= MathUtil::Deg_Rad; + shearY = (90 + shearY) * MathUtil::Deg_Rad; + float la = MathUtil::cos(shearX) * scaleX; + float lb = MathUtil::cos(shearY) * scaleY; + float lc = MathUtil::sin(shearX) * scaleX; + float ld = MathUtil::sin(shearY) * scaleY; + _a = za * la + zb * lc; + _b = za * lb + zb * ld; + _c = zc * la + zd * lc; + _d = zc * lb + zd * ld; + } + } + _a *= _skeleton.getScaleX(); + _b *= _skeleton.getScaleX(); + _c *= _skeleton.getScaleY(); + _d *= _skeleton.getScaleY(); +} + +void Bone::setToSetupPose() { + BoneData &data = _data; + _x = data.getX(); + _y = data.getY(); + _rotation = data.getRotation(); + _scaleX = data.getScaleX(); + _scaleY = data.getScaleY(); + _shearX = data.getShearX(); + _shearY = data.getShearY(); + _inherit = data.getInherit(); +} + +void Bone::worldToLocal(float worldX, float worldY, float &outLocalX, float &outLocalY) { + float a = _a; + float b = _b; + float c = _c; + float d = _d; + + float invDet = 1 / (a * d - b * c); + float x = worldX - _worldX; + float y = worldY - _worldY; + + outLocalX = (x * d * invDet - y * b * invDet); + outLocalY = (y * a * invDet - x * c * invDet); +} + +void Bone::worldToParent(float worldX, float worldY, float &outParentX, float &outParentY) { + if (!_parent) { + outParentX = worldX; + outParentY = worldY; + } else { + _parent->worldToLocal(worldX, worldY, outParentX, outParentY); + } +} + +void Bone::localToWorld(float localX, float localY, float &outWorldX, float &outWorldY) { + outWorldX = localX * _a + localY * _b + _worldX; + outWorldY = localX * _c + localY * _d + _worldY; +} + +void Bone::parentToWorld(float worldX, float worldY, float &outX, float &outY) { + if (!_parent) { + outX = worldX; + outY = worldY; + } else { + _parent->localToWorld(worldX, worldY, outX, outY); + } +} + +float Bone::worldToLocalRotation(float worldRotation) { + worldRotation *= MathUtil::Deg_Rad; + float sine = MathUtil::sin(worldRotation), cosine = MathUtil::cos(worldRotation); + return MathUtil::atan2Deg(_a * sine - _c * cosine, _d * cosine - _b * sine) + _rotation - _shearX; +} + +float Bone::localToWorldRotation(float localRotation) { + localRotation = (localRotation - _rotation - _shearX) * MathUtil::Deg_Rad; + float sine = MathUtil::sin(localRotation), cosine = MathUtil::cos(localRotation); + return MathUtil::atan2Deg(cosine * _c + sine * _d, cosine * _a + sine * _b); +} + +void Bone::rotateWorld(float degrees) { + degrees *= MathUtil::Deg_Rad; + float sine = MathUtil::sin(degrees), cosine = MathUtil::cos(degrees); + float ra = _a, rb = _b; + _a = cosine * ra - sine * _c; + _b = cosine * rb - sine * _d; + _c = sine * ra + cosine * _c; + _d = sine * rb + cosine * _d; +} + +float Bone::getWorldToLocalRotationX() { + Bone *parent = _parent; + if (!parent) { + return _arotation; + } + + float pa = parent->_a; + float pb = parent->_b; + float pc = parent->_c; + float pd = parent->_d; + float a = _a; + float c = _c; + + return MathUtil::atan2(pa * c - pc * a, pd * a - pb * c) * MathUtil::Rad_Deg; +} + +float Bone::getWorldToLocalRotationY() { + Bone *parent = _parent; + if (!parent) { + return _arotation; + } + + float pa = parent->_a; + float pb = parent->_b; + float pc = parent->_c; + float pd = parent->_d; + float b = _b; + float d = _d; + + return MathUtil::atan2(pa * d - pc * b, pd * b - pb * d) * MathUtil::Rad_Deg; +} + +BoneData &Bone::getData() { + return _data; +} + +Skeleton &Bone::getSkeleton() { + return _skeleton; +} + +Bone *Bone::getParent() { + return _parent; +} + +Vector &Bone::getChildren() { + return _children; +} + +float Bone::getX() { + return _x; +} + +void Bone::setX(float inValue) { + _x = inValue; +} + +float Bone::getY() { + return _y; +} + +void Bone::setY(float inValue) { + _y = inValue; +} + +float Bone::getRotation() { + return _rotation; +} + +void Bone::setRotation(float inValue) { + _rotation = inValue; +} + +float Bone::getScaleX() { + return _scaleX; +} + +void Bone::setScaleX(float inValue) { + _scaleX = inValue; +} + +float Bone::getScaleY() { + return _scaleY; +} + +void Bone::setScaleY(float inValue) { + _scaleY = inValue; +} + +float Bone::getShearX() { + return _shearX; +} + +void Bone::setShearX(float inValue) { + _shearX = inValue; +} + +float Bone::getShearY() { + return _shearY; +} + +void Bone::setShearY(float inValue) { + _shearY = inValue; +} + +float Bone::getAppliedRotation() { + return _arotation; +} + +void Bone::setAppliedRotation(float inValue) { + _arotation = inValue; +} + +float Bone::getAX() { + return _ax; +} + +void Bone::setAX(float inValue) { + _ax = inValue; +} + +float Bone::getAY() { + return _ay; +} + +void Bone::setAY(float inValue) { + _ay = inValue; +} + +float Bone::getAScaleX() { + return _ascaleX; +} + +void Bone::setAScaleX(float inValue) { + _ascaleX = inValue; +} + +float Bone::getAScaleY() { + return _ascaleY; +} + +void Bone::setAScaleY(float inValue) { + _ascaleY = inValue; +} + +float Bone::getAShearX() { + return _ashearX; +} + +void Bone::setAShearX(float inValue) { + _ashearX = inValue; +} + +float Bone::getAShearY() { + return _ashearY; +} + +void Bone::setAShearY(float inValue) { + _ashearY = inValue; +} + +float Bone::getA() { + return _a; +} + +void Bone::setA(float inValue) { + _a = inValue; +} + +float Bone::getB() { + return _b; +} + +void Bone::setB(float inValue) { + _b = inValue; +} + +float Bone::getC() { + return _c; +} + +void Bone::setC(float inValue) { + _c = inValue; +} + +float Bone::getD() { + return _d; +} + +void Bone::setD(float inValue) { + _d = inValue; +} + +float Bone::getWorldX() { + return _worldX; +} + +void Bone::setWorldX(float inValue) { + _worldX = inValue; +} + +float Bone::getWorldY() { + return _worldY; +} + +void Bone::setWorldY(float inValue) { + _worldY = inValue; +} + +float Bone::getWorldRotationX() { + return MathUtil::atan2Deg(_c, _a); +} + +float Bone::getWorldRotationY() { + return MathUtil::atan2Deg(_d, _b); +} + +float Bone::getWorldScaleX() { + return MathUtil::sqrt(_a * _a + _c * _c); +} + +float Bone::getWorldScaleY() { + return MathUtil::sqrt(_b * _b + _d * _d); +} + +void Bone::updateAppliedTransform() { + Bone *parent = _parent; + if (!parent) { + _ax = _worldX - _skeleton.getX(); + _ay = _worldY - _skeleton.getY(); + _arotation = MathUtil::atan2Deg(_c, _a); + _ascaleX = MathUtil::sqrt(_a * _a + _c * _c); + _ascaleY = MathUtil::sqrt(_b * _b + _d * _d); + _ashearX = 0; + _ashearY = MathUtil::atan2Deg(_a * _b + _c * _d, _a * _d - _b * _c); + } + float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d; + float pid = 1 / (pa * pd - pb * pc); + float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid; + float dx = _worldX - parent->_worldX, dy = _worldY - parent->_worldY; + _ax = (dx * ia - dy * ib); + _ay = (dy * id - dx * ic); + + float ra, rb, rc, rd; + if (_inherit == Inherit_OnlyTranslation) { + ra = _a; + rb = _b; + rc = _c; + rd = _d; + } else { + switch (_inherit) { + case Inherit_NoRotationOrReflection: { + float s = MathUtil::abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + float sa = pa / _skeleton.getScaleX(); + float sc = pc / _skeleton.getScaleY(); + pb = -sc * s * _skeleton.getScaleX(); + pd = sa * s * _skeleton.getScaleY(); + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + break; + } + case Inherit_NoScale: + case Inherit_NoScaleOrReflection: { + float r = _rotation * MathUtil::Deg_Rad; + float cos = MathUtil::cos(r), sin = MathUtil::sin(r); + pa = (pa * cos + pb * sin) / _skeleton.getScaleX(); + pc = (pc * cos + pd * sin) / _skeleton.getScaleY(); + float s = MathUtil::sqrt(pa * pa + pc * pc); + if (s > 0.00001) s = 1 / s; + pa *= s; + pc *= s; + s = MathUtil::sqrt(pa * pa + pc * pc); + if (_inherit == Inherit_NoScale && + pid < 0 != (_skeleton.getScaleX() < 0 != _skeleton.getScaleY() < 0)) + s = -s; + r = MathUtil::Pi / 2 + MathUtil::atan2(pc, pa); + pb = MathUtil::cos(r) * s; + pd = MathUtil::sin(r) * s; + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + ic = pc * pid; + id = pa * pid; + break; + } + case Inherit_Normal: + case Inherit_OnlyTranslation: + break; + } + ra = ia * _a - ib * _c; + rb = ia * _b - ib * _d; + rc = id * _c - ic * _a; + rd = id * _d - ic * _b; + } + + _ashearX = 0; + _ascaleX = MathUtil::sqrt(ra * ra + rc * rc); + if (_ascaleX > 0.0001f) { + float det = ra * rd - rb * rc; + _ascaleY = det / _ascaleX; + _ashearY = -MathUtil::atan2Deg(ra * rb + rc * rd, det); + _arotation = MathUtil::atan2Deg(rc, ra); + } else { + _ascaleX = 0; + _ascaleY = MathUtil::sqrt(rb * rb + rd * rd); + _ashearY = 0; + _arotation = 90 - MathUtil::atan2Deg(rd, rb); + } +} + +bool Bone::isActive() { + return _active; +} + +void Bone::setActive(bool inValue) { + _active = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/BoneData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/BoneData.cpp new file mode 100644 index 0000000..31ab264 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/BoneData.cpp @@ -0,0 +1,166 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +BoneData::BoneData(int index, const String &name, BoneData *parent) : _index(index), + _name(name), + _parent(parent), + _length(0), + _x(0), + _y(0), + _rotation(0), + _scaleX(1), + _scaleY(1), + _shearX(0), + _shearY(0), + _inherit(Inherit_Normal), + _skinRequired(false), + _color(), + _icon(), + _visible(true) { + assert(index >= 0); + assert(_name.length() > 0); +} + +int BoneData::getIndex() { + return _index; +} + +const String &BoneData::getName() { + return _name; +} + +BoneData *BoneData::getParent() { + return _parent; +} + +float BoneData::getLength() { + return _length; +} + +void BoneData::setLength(float inValue) { + _length = inValue; +} + +float BoneData::getX() { + return _x; +} + +void BoneData::setX(float inValue) { + _x = inValue; +} + +float BoneData::getY() { + return _y; +} + +void BoneData::setY(float inValue) { + _y = inValue; +} + +float BoneData::getRotation() { + return _rotation; +} + +void BoneData::setRotation(float inValue) { + _rotation = inValue; +} + +float BoneData::getScaleX() { + return _scaleX; +} + +void BoneData::setScaleX(float inValue) { + _scaleX = inValue; +} + +float BoneData::getScaleY() { + return _scaleY; +} + +void BoneData::setScaleY(float inValue) { + _scaleY = inValue; +} + +float BoneData::getShearX() { + return _shearX; +} + +void BoneData::setShearX(float inValue) { + _shearX = inValue; +} + +float BoneData::getShearY() { + return _shearY; +} + +void BoneData::setShearY(float inValue) { + _shearY = inValue; +} + +Inherit BoneData::getInherit() { + return _inherit; +} + +void BoneData::setInherit(Inherit inValue) { + _inherit = inValue; +} + +bool BoneData::isSkinRequired() { + return _skinRequired; +} + +void BoneData::setSkinRequired(bool inValue) { + _skinRequired = inValue; +} + +Color &BoneData::getColor() { + return _color; +} + +const String &BoneData::getIcon() { + return _icon; +} + +void BoneData::setIcon(const String &icon) { + this->_icon = icon; +} + +bool BoneData::isVisible() { + return _visible; +} + +void BoneData::setVisible(bool inValue) { + this->_visible = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/BoundingBoxAttachment.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/BoundingBoxAttachment.cpp new file mode 100644 index 0000000..5ede2c5 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/BoundingBoxAttachment.cpp @@ -0,0 +1,47 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL(BoundingBoxAttachment, VertexAttachment) + +BoundingBoxAttachment::BoundingBoxAttachment(const String &name) : VertexAttachment(name), _color() { +} + +Color &BoundingBoxAttachment::getColor() { + return _color; +} + +Attachment *BoundingBoxAttachment::copy() { + BoundingBoxAttachment *copy = new (__FILE__, __LINE__) BoundingBoxAttachment(getName()); + copyTo(copy); + return copy; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ClippingAttachment.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ClippingAttachment.cpp new file mode 100644 index 0000000..90f5c14 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ClippingAttachment.cpp @@ -0,0 +1,58 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +RTTI_IMPL(ClippingAttachment, VertexAttachment) + +ClippingAttachment::ClippingAttachment(const String &name) : VertexAttachment(name), _endSlot(NULL), _color() { +} + +SlotData *ClippingAttachment::getEndSlot() { + return _endSlot; +} + +void ClippingAttachment::setEndSlot(SlotData *inValue) { + _endSlot = inValue; +} + +Color &ClippingAttachment::getColor() { + return _color; +} + +Attachment *ClippingAttachment::copy() { + ClippingAttachment *copy = new (__FILE__, __LINE__) ClippingAttachment(getName()); + copyTo(copy); + copy->_endSlot = _endSlot; + return copy; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ColorTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ColorTimeline.cpp new file mode 100644 index 0000000..ce7ed25 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ColorTimeline.cpp @@ -0,0 +1,493 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(RGBATimeline, CurveTimeline) + +RGBATimeline::RGBATimeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGBATimeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, + ((PropertyId) Property_Alpha << 32) | slotIndex}; + setPropertyIds(ids, 2); +} + +RGBATimeline::~RGBATimeline() { +} + +void RGBATimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &color = slot->_color, &setup = slot->_data._color; + switch (blend) { + case MixBlend_Setup: + color.set(setup); + return; + case MixBlend_First: + color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha); + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0, a = 0; + int i = Animation::search(_frames, time, RGBATimeline::ENTRIES); + int curveType = (int) _curves[i / RGBATimeline::ENTRIES]; + switch (curveType) { + case RGBATimeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGBATimeline::R]; + g = _frames[i + RGBATimeline::G]; + b = _frames[i + RGBATimeline::B]; + a = _frames[i + RGBATimeline::A]; + float t = (time - before) / (_frames[i + RGBATimeline::ENTRIES] - before); + r += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::R] - r) * t; + g += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::G] - g) * t; + b += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::B] - b) * t; + a += (_frames[i + RGBATimeline::ENTRIES + RGBATimeline::A] - a) * t; + break; + } + case RGBATimeline::STEPPED: { + r = _frames[i + RGBATimeline::R]; + g = _frames[i + RGBATimeline::G]; + b = _frames[i + RGBATimeline::B]; + a = _frames[i + RGBATimeline::A]; + break; + } + default: { + r = getBezierValue(time, i, RGBATimeline::R, curveType - RGBATimeline::BEZIER); + g = getBezierValue(time, i, RGBATimeline::G, + curveType + RGBATimeline::BEZIER_SIZE - RGBATimeline::BEZIER); + b = getBezierValue(time, i, RGBATimeline::B, + curveType + RGBATimeline::BEZIER_SIZE * 2 - RGBATimeline::BEZIER); + a = getBezierValue(time, i, RGBATimeline::A, + curveType + RGBATimeline::BEZIER_SIZE * 3 - RGBATimeline::BEZIER); + } + } + Color &color = slot->_color; + if (alpha == 1) + color.set(r, g, b, a); + else { + if (blend == MixBlend_Setup) color.set(slot->_data._color); + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); + } +} + +void RGBATimeline::setFrame(int frame, float time, float r, float g, float b, float a) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + R] = r; + _frames[frame + G] = g; + _frames[frame + B] = b; + _frames[frame + A] = a; +} + +RTTI_IMPL(RGBTimeline, CurveTimeline) + +RGBTimeline::RGBTimeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGBTimeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex}; + setPropertyIds(ids, 1); +} + +RGBTimeline::~RGBTimeline() { +} + +void RGBTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &color = slot->_color, &setup = slot->_data._color; + switch (blend) { + case MixBlend_Setup: + color.set(setup); + return; + case MixBlend_First: + color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha); + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0; + int i = Animation::search(_frames, time, RGBTimeline::ENTRIES); + int curveType = (int) _curves[i / RGBTimeline::ENTRIES]; + switch (curveType) { + case RGBTimeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGBTimeline::R]; + g = _frames[i + RGBTimeline::G]; + b = _frames[i + RGBTimeline::B]; + float t = (time - before) / (_frames[i + RGBTimeline::ENTRIES] - before); + r += (_frames[i + RGBTimeline::ENTRIES + RGBTimeline::R] - r) * t; + g += (_frames[i + RGBTimeline::ENTRIES + RGBTimeline::G] - g) * t; + b += (_frames[i + RGBTimeline::ENTRIES + RGBTimeline::B] - b) * t; + break; + } + case RGBTimeline::STEPPED: { + r = _frames[i + RGBTimeline::R]; + g = _frames[i + RGBTimeline::G]; + b = _frames[i + RGBTimeline::B]; + break; + } + default: { + r = getBezierValue(time, i, RGBTimeline::R, curveType - RGBTimeline::BEZIER); + g = getBezierValue(time, i, RGBTimeline::G, + curveType + RGBTimeline::BEZIER_SIZE - RGBTimeline::BEZIER); + b = getBezierValue(time, i, RGBTimeline::B, + curveType + RGBTimeline::BEZIER_SIZE * 2 - RGBTimeline::BEZIER); + } + } + Color &color = slot->_color; + if (alpha == 1) + color.set(r, g, b); + else { + Color &setup = slot->_data._color; + if (blend == MixBlend_Setup) color.set(setup.r, setup.g, setup.b); + color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha); + } +} + +void RGBTimeline::setFrame(int frame, float time, float r, float g, float b) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + R] = r; + _frames[frame + G] = g; + _frames[frame + B] = b; +} + +RTTI_IMPL(AlphaTimeline, CurveTimeline1) + +AlphaTimeline::AlphaTimeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline1(frameCount, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Alpha << 32) | slotIndex}; + setPropertyIds(ids, 1); +} + +AlphaTimeline::~AlphaTimeline() { +} + +void AlphaTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) {// Time is before first frame. + Color &color = slot->_color, &setup = slot->_data._color; + switch (blend) { + case MixBlend_Setup: + color.a = setup.a; + return; + case MixBlend_First: + color.a += (setup.a - color.a) * alpha; + default: { + } + } + return; + } + + float a = getCurveValue(time); + if (alpha == 1) + slot->_color.a = a; + else { + if (blend == MixBlend_Setup) slot->_color.a = slot->_data._color.a; + slot->_color.a += (a - slot->_color.a) * alpha; + } +} + +RTTI_IMPL(RGBA2Timeline, CurveTimeline) + +RGBA2Timeline::RGBA2Timeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGBA2Timeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, + ((PropertyId) Property_Alpha << 32) | slotIndex, + ((PropertyId) Property_Rgb2 << 32) | slotIndex}; + setPropertyIds(ids, 3); +} + +RGBA2Timeline::~RGBA2Timeline() { +} + +void RGBA2Timeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &light = slot->_color, &dark = slot->_darkColor, &setupLight = slot->_data._color, &setupDark = slot->_data._darkColor; + switch (blend) { + case MixBlend_Setup: + light.set(setupLight); + dark.set(setupDark.r, setupDark.g, setupDark.b); + return; + case MixBlend_First: + light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, + (setupLight.b - light.b) * alpha, + (setupLight.a - light.a) * alpha); + dark.r += (setupDark.r - dark.r) * alpha; + dark.g += (setupDark.g - dark.g) * alpha; + dark.b += (setupDark.b - dark.b) * alpha; + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0, a = 0, r2 = 0, g2 = 0, b2 = 0; + int i = Animation::search(_frames, time, RGBA2Timeline::ENTRIES); + int curveType = (int) _curves[i / RGBA2Timeline::ENTRIES]; + switch (curveType) { + case RGBA2Timeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGBA2Timeline::R]; + g = _frames[i + RGBA2Timeline::G]; + b = _frames[i + RGBA2Timeline::B]; + a = _frames[i + RGBA2Timeline::A]; + r2 = _frames[i + RGBA2Timeline::R2]; + g2 = _frames[i + RGBA2Timeline::G2]; + b2 = _frames[i + RGBA2Timeline::B2]; + float t = (time - before) / (_frames[i + RGBA2Timeline::ENTRIES] - before); + r += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::R] - r) * t; + g += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::G] - g) * t; + b += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::B] - b) * t; + a += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::A] - a) * t; + r2 += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::R2] - r2) * t; + g2 += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::G2] - g2) * t; + b2 += (_frames[i + RGBA2Timeline::ENTRIES + RGBA2Timeline::B2] - b2) * t; + break; + } + case RGBA2Timeline::STEPPED: { + r = _frames[i + RGBA2Timeline::R]; + g = _frames[i + RGBA2Timeline::G]; + b = _frames[i + RGBA2Timeline::B]; + a = _frames[i + RGBA2Timeline::A]; + r2 = _frames[i + RGBA2Timeline::R2]; + g2 = _frames[i + RGBA2Timeline::G2]; + b2 = _frames[i + RGBA2Timeline::B2]; + break; + } + default: { + r = getBezierValue(time, i, RGBA2Timeline::R, curveType - RGBA2Timeline::BEZIER); + g = getBezierValue(time, i, RGBA2Timeline::G, + curveType + RGBA2Timeline::BEZIER_SIZE - RGBA2Timeline::BEZIER); + b = getBezierValue(time, i, RGBA2Timeline::B, + curveType + RGBA2Timeline::BEZIER_SIZE * 2 - RGBA2Timeline::BEZIER); + a = getBezierValue(time, i, RGBA2Timeline::A, + curveType + RGBA2Timeline::BEZIER_SIZE * 3 - RGBA2Timeline::BEZIER); + r2 = getBezierValue(time, i, RGBA2Timeline::R2, + curveType + RGBA2Timeline::BEZIER_SIZE * 4 - RGBA2Timeline::BEZIER); + g2 = getBezierValue(time, i, RGBA2Timeline::G2, + curveType + RGBA2Timeline::BEZIER_SIZE * 5 - RGBA2Timeline::BEZIER); + b2 = getBezierValue(time, i, RGBA2Timeline::B2, + curveType + RGBA2Timeline::BEZIER_SIZE * 6 - RGBA2Timeline::BEZIER); + } + } + Color &light = slot->_color, &dark = slot->_darkColor; + if (alpha == 1) { + light.set(r, g, b, a); + dark.set(r2, g2, b2); + } else { + if (blend == MixBlend_Setup) { + light.set(slot->_data._color); + dark.set(slot->_data._darkColor); + } + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha); + dark.r += (r2 - dark.r) * alpha; + dark.g += (g2 - dark.g) * alpha; + dark.b += (b2 - dark.b) * alpha; + } +} + +void RGBA2Timeline::setFrame(int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + R] = r; + _frames[frame + G] = g; + _frames[frame + B] = b; + _frames[frame + A] = a; + _frames[frame + R2] = r2; + _frames[frame + G2] = g2; + _frames[frame + B2] = b2; +} + +RTTI_IMPL(RGB2Timeline, CurveTimeline) + +RGB2Timeline::RGB2Timeline(size_t frameCount, size_t bezierCount, int slotIndex) : CurveTimeline(frameCount, + RGB2Timeline::ENTRIES, + bezierCount), + _slotIndex(slotIndex) { + PropertyId ids[] = {((PropertyId) Property_Rgb << 32) | slotIndex, + ((PropertyId) Property_Rgb2 << 32) | slotIndex}; + setPropertyIds(ids, 2); +} + +RGB2Timeline::~RGB2Timeline() { +} + +void RGB2Timeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton._slots[_slotIndex]; + if (!slot->_bone._active) return; + + if (time < _frames[0]) { + Color &light = slot->_color, &dark = slot->_darkColor, &setupLight = slot->_data._color, &setupDark = slot->_data._darkColor; + switch (blend) { + case MixBlend_Setup: + light.set(setupLight.r, setupLight.g, setupLight.b); + dark.set(setupDark.r, setupDark.g, setupDark.b); + return; + case MixBlend_First: + light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, + (setupLight.b - light.b) * alpha); + dark.r += (setupDark.r - dark.r) * alpha; + dark.g += (setupDark.g - dark.g) * alpha; + dark.b += (setupDark.b - dark.b) * alpha; + default: { + } + } + return; + } + + float r = 0, g = 0, b = 0, r2 = 0, g2 = 0, b2 = 0; + int i = Animation::search(_frames, time, RGB2Timeline::ENTRIES); + int curveType = (int) _curves[i / RGB2Timeline::ENTRIES]; + switch (curveType) { + case RGB2Timeline::LINEAR: { + float before = _frames[i]; + r = _frames[i + RGB2Timeline::R]; + g = _frames[i + RGB2Timeline::G]; + b = _frames[i + RGB2Timeline::B]; + r2 = _frames[i + RGB2Timeline::R2]; + g2 = _frames[i + RGB2Timeline::G2]; + b2 = _frames[i + RGB2Timeline::B2]; + float t = (time - before) / (_frames[i + RGB2Timeline::ENTRIES] - before); + r += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::R] - r) * t; + g += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::G] - g) * t; + b += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::B] - b) * t; + r2 += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::R2] - r2) * t; + g2 += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::G2] - g2) * t; + b2 += (_frames[i + RGB2Timeline::ENTRIES + RGB2Timeline::B2] - b2) * t; + break; + } + case RGB2Timeline::STEPPED: { + r = _frames[i + RGB2Timeline::R]; + g = _frames[i + RGB2Timeline::G]; + b = _frames[i + RGB2Timeline::B]; + r2 = _frames[i + RGB2Timeline::R2]; + g2 = _frames[i + RGB2Timeline::G2]; + b2 = _frames[i + RGB2Timeline::B2]; + break; + } + default: { + r = getBezierValue(time, i, RGB2Timeline::R, curveType - RGB2Timeline::BEZIER); + g = getBezierValue(time, i, RGB2Timeline::G, + curveType + RGB2Timeline::BEZIER_SIZE - RGB2Timeline::BEZIER); + b = getBezierValue(time, i, RGB2Timeline::B, + curveType + RGB2Timeline::BEZIER_SIZE * 2 - RGB2Timeline::BEZIER); + r2 = getBezierValue(time, i, RGB2Timeline::R2, + curveType + RGB2Timeline::BEZIER_SIZE * 4 - RGB2Timeline::BEZIER); + g2 = getBezierValue(time, i, RGB2Timeline::G2, + curveType + RGB2Timeline::BEZIER_SIZE * 5 - RGB2Timeline::BEZIER); + b2 = getBezierValue(time, i, RGB2Timeline::B2, + curveType + RGB2Timeline::BEZIER_SIZE * 6 - RGB2Timeline::BEZIER); + } + } + Color &light = slot->_color, &dark = slot->_darkColor; + if (alpha == 1) { + light.set(r, g, b); + dark.set(r2, g2, b2); + } else { + if (blend == MixBlend_Setup) { + light.set(slot->_data._color.r, slot->_data._color.g, slot->_data._color.b); + dark.set(slot->_data._darkColor); + } + light.add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha); + dark.r += (r2 - dark.r) * alpha; + dark.g += (g2 - dark.g) * alpha; + dark.b += (b2 - dark.b) * alpha; + } +} + +void RGB2Timeline::setFrame(int frame, float time, float r, float g, float b, float r2, float g2, float b2) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + R] = r; + _frames[frame + G] = g; + _frames[frame + B] = b; + _frames[frame + R2] = r2; + _frames[frame + G2] = g2; + _frames[frame + B2] = b2; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ConstraintData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ConstraintData.cpp new file mode 100644 index 0000000..1be0b2d --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ConstraintData.cpp @@ -0,0 +1,60 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(ConstraintData) + +ConstraintData::ConstraintData(const String &name) : _name(name), _order(0), _skinRequired(false) { +} + +ConstraintData::~ConstraintData() { +} + +const String &ConstraintData::getName() { + return _name; +} + +size_t ConstraintData::getOrder() { + return _order; +} + +void ConstraintData::setOrder(size_t inValue) { + _order = inValue; +} + +bool ConstraintData::isSkinRequired() { + return _skinRequired; +} + +void ConstraintData::setSkinRequired(bool inValue) { + _skinRequired = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/CurveTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/CurveTimeline.cpp new file mode 100644 index 0000000..530dabd --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/CurveTimeline.cpp @@ -0,0 +1,252 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +RTTI_IMPL(CurveTimeline, Timeline) + +CurveTimeline::CurveTimeline(size_t frameCount, size_t frameEntries, size_t bezierCount) : Timeline(frameCount, + frameEntries) { + _curves.setSize(frameCount + bezierCount * BEZIER_SIZE, 0); + _curves[frameCount - 1] = STEPPED; +} + +CurveTimeline::~CurveTimeline() { +} + +void CurveTimeline::setLinear(size_t frame) { + _curves[frame] = LINEAR; +} + +void CurveTimeline::setStepped(size_t frame) { + _curves[frame] = STEPPED; +} + +void CurveTimeline::setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, + float cx2, float cy2, float time2, float value2) { + size_t i = getFrameCount() + bezier * BEZIER_SIZE; + if (value == 0) _curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = (value1 - cy1 * 2 + cy2) * 0.03; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = (cy1 - value1) * 0.3 + tmpy + dddy * 0.16666667; + float x = time1 + dx, y = value1 + dy; + for (size_t n = i + BEZIER_SIZE; i < n; i += 2) { + _curves[i] = x; + _curves[i + 1] = y; + dx += ddx; + dy += ddy; + ddx += dddx; + ddy += dddy; + x += dx; + y += dy; + } +} + +float CurveTimeline::getBezierValue(float time, size_t frameIndex, size_t valueOffset, size_t i) { + if (_curves[i] > time) { + float x = _frames[frameIndex], y = _frames[frameIndex + valueOffset]; + return y + (time - x) / (_curves[i] - x) * (_curves[i + 1] - y); + } + size_t n = i + BEZIER_SIZE; + for (i += 2; i < n; i += 2) { + if (_curves[i] >= time) { + float x = _curves[i - 2], y = _curves[i - 1]; + return y + (time - x) / (_curves[i] - x) * (_curves[i + 1] - y); + } + } + frameIndex += getFrameEntries(); + float x = _curves[n - 2], y = _curves[n - 1]; + return y + (time - x) / (_frames[frameIndex] - x) * (_frames[frameIndex + valueOffset] - y); +} + +Vector &CurveTimeline::getCurves() { + return _curves; +} + +RTTI_IMPL(CurveTimeline1, CurveTimeline) + +CurveTimeline1::CurveTimeline1(size_t frameCount, size_t bezierCount) : CurveTimeline(frameCount, + CurveTimeline1::ENTRIES, + bezierCount) { +} + +CurveTimeline1::~CurveTimeline1() { +} + +void CurveTimeline1::setFrame(size_t frame, float time, float value) { + frame <<= 1; + _frames[frame] = time; + _frames[frame + CurveTimeline1::VALUE] = value; +} + +float CurveTimeline1::getCurveValue(float time) { + int i = (int) _frames.size() - 2; + for (int ii = 2; ii <= i; ii += 2) { + if (_frames[ii] > time) { + i = ii - 2; + break; + } + } + + int curveType = (int) _curves[i >> 1]; + switch (curveType) { + case CurveTimeline::LINEAR: { + float before = _frames[i], value = _frames[i + CurveTimeline1::VALUE]; + return value + (time - before) / (_frames[i + CurveTimeline1::ENTRIES] - before) * + (_frames[i + CurveTimeline1::ENTRIES + CurveTimeline1::VALUE] - value); + } + case CurveTimeline::STEPPED: + return _frames[i + CurveTimeline1::VALUE]; + } + return getBezierValue(time, i, CurveTimeline1::VALUE, curveType - CurveTimeline1::BEZIER); +} + +float CurveTimeline1::getRelativeValue(float time, float alpha, MixBlend blend, float current, float setup) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + float value = getCurveValue(time); + switch (blend) { + case MixBlend_Setup: + return setup + value * alpha; + case MixBlend_First: + case MixBlend_Replace: + value += setup - current; + break; + case MixBlend_Add: + break; + } + return current + value * alpha; +} + +float CurveTimeline1::getAbsoluteValue(float time, float alpha, MixBlend blend, float current, float setup) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + float value = getCurveValue(time); + if (blend == MixBlend_Setup) return setup + (value - setup) * alpha; + return current + (value - current) * alpha; +} + +float CurveTimeline1::getAbsoluteValue(float time, float alpha, MixBlend blend, float current, float setup, float value) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + if (blend == MixBlend_Setup) return setup + (value - setup) * alpha; + return current + (value - current) * alpha; +} + +float CurveTimeline1::getScaleValue(float time, float alpha, MixBlend blend, MixDirection direction, float current, + float setup) { + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + return setup; + case MixBlend_First: + return current + (setup - current) * alpha; + default: + return current; + } + } + float value = getCurveValue(time) * setup; + if (alpha == 1) { + if (blend == MixBlend_Add) return current + value - setup; + return value; + } + // Mixing out uses sign of setup or current pose, else use sign of key. + if (direction == MixDirection_Out) { + switch (blend) { + case MixBlend_Setup: + return setup + (MathUtil::abs(value) * MathUtil::sign(setup) - setup) * alpha; + case MixBlend_First: + case MixBlend_Replace: + return current + (MathUtil::abs(value) * MathUtil::sign(current) - current) * alpha; + default: + break; + } + } else { + float s; + switch (blend) { + case MixBlend_Setup: + s = MathUtil::abs(setup) * MathUtil::sign(value); + return s + (value - s) * alpha; + case MixBlend_First: + case MixBlend_Replace: + s = MathUtil::abs(current) * MathUtil::sign(value); + return s + (value - s) * alpha; + default: + break; + } + } + return current + (value - setup) * alpha; +} + + +RTTI_IMPL(CurveTimeline2, CurveTimeline) + +CurveTimeline2::CurveTimeline2(size_t frameCount, size_t bezierCount) : CurveTimeline(frameCount, + CurveTimeline2::ENTRIES, + bezierCount) { +} + +CurveTimeline2::~CurveTimeline2() { +} + +void CurveTimeline2::setFrame(size_t frame, float time, float value1, float value2) { + frame *= CurveTimeline2::ENTRIES; + _frames[frame] = time; + _frames[frame + CurveTimeline2::VALUE1] = value1; + _frames[frame + CurveTimeline2::VALUE2] = value2; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/DeformTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/DeformTimeline.cpp new file mode 100644 index 0000000..d7d5aa1 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/DeformTimeline.cpp @@ -0,0 +1,328 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(DeformTimeline, CurveTimeline) + +DeformTimeline::DeformTimeline(size_t frameCount, size_t bezierCount, int slotIndex, VertexAttachment *attachment) + : CurveTimeline(frameCount, 1, bezierCount), _slotIndex(slotIndex), _attachment(attachment) { + PropertyId ids[] = {((PropertyId) Property_Deform << 32) | ((slotIndex << 16 | attachment->_id) & 0xffffffff)}; + setPropertyIds(ids, 1); + + _vertices.ensureCapacity(frameCount); + for (size_t i = 0; i < frameCount; ++i) { + Vector vec; + _vertices.add(vec); + } +} + +void DeformTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slotP = skeleton._slots[_slotIndex]; + Slot &slot = *slotP; + if (!slot._bone.isActive()) return; + + Attachment *slotAttachment = slot.getAttachment(); + if (slotAttachment == NULL || !slotAttachment->getRTTI().instanceOf(VertexAttachment::rtti)) { + return; + } + + VertexAttachment *attachment = static_cast(slotAttachment); + if (attachment->_timelineAttachment != _attachment) { + return; + } + + Vector &deformArray = slot._deform; + if (deformArray.size() == 0) { + blend = MixBlend_Setup; + } + + Vector> &vertices = _vertices; + size_t vertexCount = vertices[0].size(); + + Vector &frames = _frames; + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + deformArray.clear(); + return; + case MixBlend_First: { + if (alpha == 1) { + deformArray.clear(); + return; + } + deformArray.setSize(vertexCount, 0); + Vector &deform = deformArray; + if (attachment->getBones().size() == 0) { + // Unweighted vertex positions. + Vector &setupVertices = attachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (setupVertices[i] - deform[i]) * alpha; + } else { + // Weighted deform offsets. + alpha = 1 - alpha; + for (size_t i = 0; i < vertexCount; i++) + deform[i] *= alpha; + } + } + case MixBlend_Replace: + case MixBlend_Add: { + } + } + return; + } + + deformArray.setSize(vertexCount, 0); + Vector &deform = deformArray; + + if (time >= frames[frames.size() - 1]) {// Time is after last frame. + Vector &lastVertices = vertices[frames.size() - 1]; + if (alpha == 1) { + if (blend == MixBlend_Add) { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] - setupVertices[i]; + } else { + // Weighted deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i]; + } + } else { + // Vertex positions or deform offsets, no alpha. + memcpy(deform.buffer(), lastVertices.buffer(), vertexCount * sizeof(float)); + } + } else { + switch (blend) { + case MixBlend_Setup: { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] = lastVertices[i] * alpha; + } + break; + } + case MixBlend_First: + case MixBlend_Replace: + // Vertex positions or deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend_Add: + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } else { + // Weighted deform offsets, alpha. + for (size_t i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; + } + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation::search(frames, time); + float percent = getCurvePercent(time, frame); + Vector &prevVertices = vertices[frame]; + Vector &nextVertices = vertices[frame + 1]; + + if (alpha == 1) { + if (blend == MixBlend_Add) { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, no alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i]; + } + } else { + // Weighted deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += prev + (nextVertices[i] - prev) * percent; + } + } + } else { + // Vertex positions or deform offsets, no alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } else { + switch (blend) { + case MixBlend_Setup: { + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i], setup = setupVertices[i]; + deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + case MixBlend_First: + case MixBlend_Replace: + // Vertex positions or deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + case MixBlend_Add: + VertexAttachment *vertexAttachment = static_cast(slotAttachment); + if (vertexAttachment->getBones().size() == 0) { + // Unweighted vertex positions, with alpha. + Vector &setupVertices = vertexAttachment->getVertices(); + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (size_t i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } + } +} + +void DeformTimeline::setBezier(size_t bezier, size_t frame, float value, float time1, float value1, float cx1, float cy1, + float cx2, float cy2, float time2, float value2) { + SP_UNUSED(value1); + SP_UNUSED(value2); + size_t i = getFrameCount() + bezier * DeformTimeline::BEZIER_SIZE; + if (value == 0) _curves[frame] = DeformTimeline::BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03, tmpy = cy2 * 0.03 - cy1 * 0.06; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006, dddy = (cy1 - cy2 + 0.33333333) * 0.018; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667, dy = cy1 * 0.3 + tmpy + dddy * 0.16666667; + float x = time1 + dx, y = dy; + for (size_t n = i + DeformTimeline::BEZIER_SIZE; i < n; i += 2) { + _curves[i] = x; + _curves[i + 1] = y; + dx += ddx; + dy += ddy; + ddx += dddx; + ddy += dddy; + x += dx; + y += dy; + } +} + +float DeformTimeline::getCurvePercent(float time, int frame) { + int i = (int) _curves[frame]; + switch (i) { + case DeformTimeline::LINEAR: { + float x = _frames[frame]; + return (time - x) / (_frames[frame + getFrameEntries()] - x); + } + case DeformTimeline::STEPPED: { + return 0; + } + default: { + } + } + i -= DeformTimeline::BEZIER; + if (_curves[i] > time) { + float x = _frames[frame]; + return _curves[i + 1] * (time - x) / (_curves[i] - x); + } + int n = i + DeformTimeline::BEZIER_SIZE; + for (i += 2; i < n; i += 2) { + if (_curves[i] >= time) { + float x = _curves[i - 2], y = _curves[i - 1]; + return y + (time - x) / (_curves[i] - x) * (_curves[i + 1] - y); + } + } + float x = _curves[n - 2], y = _curves[n - 1]; + return y + (1 - y) * (time - x) / (_frames[frame + getFrameEntries()] - x); +} + +void DeformTimeline::setFrame(int frame, float time, Vector &vertices) { + _frames[frame] = time; + _vertices[frame].clear(); + _vertices[frame].addAll(vertices); +} + +Vector> &DeformTimeline::getVertices() { + return _vertices; +} + +VertexAttachment *DeformTimeline::getAttachment() { + return _attachment; +} + +void DeformTimeline::setAttachment(VertexAttachment *inValue) { + _attachment = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/DrawOrderTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/DrawOrderTimeline.cpp new file mode 100644 index 0000000..2708d25 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/DrawOrderTimeline.cpp @@ -0,0 +1,102 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(DrawOrderTimeline, Timeline) + +DrawOrderTimeline::DrawOrderTimeline(size_t frameCount) : Timeline(frameCount, 1) { + PropertyId ids[] = {((PropertyId) Property_DrawOrder << 32)}; + setPropertyIds(ids, 1); + + _drawOrders.ensureCapacity(frameCount); + for (size_t i = 0; i < frameCount; ++i) { + Vector vec; + _drawOrders.add(vec); + } +} + +void DrawOrderTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(alpha); + + Vector &drawOrder = skeleton._drawOrder; + Vector &slots = skeleton._slots; + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) { + drawOrder.clear(); + drawOrder.ensureCapacity(slots.size()); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) { + drawOrder.clear(); + drawOrder.ensureCapacity(slots.size()); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } + return; + } + + Vector &drawOrderToSetupIndex = _drawOrders[Animation::search(_frames, time)]; + if (drawOrderToSetupIndex.size() == 0) { + drawOrder.clear(); + for (size_t i = 0, n = slots.size(); i < n; ++i) + drawOrder.add(slots[i]); + } else { + for (size_t i = 0, n = drawOrderToSetupIndex.size(); i < n; ++i) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } +} + +void DrawOrderTimeline::setFrame(size_t frame, float time, Vector &drawOrder) { + _frames[frame] = time; + _drawOrders[frame].clear(); + _drawOrders[frame].addAll(drawOrder); +} + +Vector> &DrawOrderTimeline::getDrawOrders() { + return _drawOrders; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Event.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Event.cpp new file mode 100644 index 0000000..3086184 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Event.cpp @@ -0,0 +1,90 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +spine::Event::Event(float time, const spine::EventData &data) : _data(data), + _time(time), + _intValue(0), + _floatValue(0), + _stringValue(), + _volume(1), + _balance(0) { +} + +const spine::EventData &spine::Event::getData() { + return _data; +} + +float spine::Event::getTime() { + return _time; +} + +int spine::Event::getIntValue() { + return _intValue; +} + +void spine::Event::setIntValue(int inValue) { + _intValue = inValue; +} + +float spine::Event::getFloatValue() { + return _floatValue; +} + +void spine::Event::setFloatValue(float inValue) { + _floatValue = inValue; +} + +const spine::String &spine::Event::getStringValue() { + return _stringValue; +} + +void spine::Event::setStringValue(const spine::String &inValue) { + _stringValue = inValue; +} + + +float spine::Event::getVolume() { + return _volume; +} + +void spine::Event::setVolume(float inValue) { + _volume = inValue; +} + +float spine::Event::getBalance() { + return _balance; +} + +void spine::Event::setBalance(float inValue) { + _balance = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/EventData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/EventData.cpp new file mode 100644 index 0000000..df72041 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/EventData.cpp @@ -0,0 +1,96 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +spine::EventData::EventData(const spine::String &name) : _name(name), + _intValue(0), + _floatValue(0), + _stringValue(), + _audioPath(), + _volume(1), + _balance(0) { + assert(_name.length() > 0); +} + +/// The name of the event, which is unique within the skeleton. +const spine::String &spine::EventData::getName() const { + return _name; +} + +int spine::EventData::getIntValue() const { + return _intValue; +} + +void spine::EventData::setIntValue(int inValue) { + _intValue = inValue; +} + +float spine::EventData::getFloatValue() const { + return _floatValue; +} + +void spine::EventData::setFloatValue(float inValue) { + _floatValue = inValue; +} + +const spine::String &spine::EventData::getStringValue() const { + return _stringValue; +} + +void spine::EventData::setStringValue(const spine::String &inValue) { + this->_stringValue = inValue; +} + +const spine::String &spine::EventData::getAudioPath() const { + return _audioPath; +} + +void spine::EventData::setAudioPath(const spine::String &inValue) { + _audioPath = inValue; +} + + +float spine::EventData::getVolume() const { + return _volume; +} + +void spine::EventData::setVolume(float inValue) { + _volume = inValue; +} + +float spine::EventData::getBalance() const { + return _balance; +} + +void spine::EventData::setBalance(float inValue) { + _balance = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/EventTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/EventTimeline.cpp new file mode 100644 index 0000000..aa5986f --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/EventTimeline.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(EventTimeline, Timeline) + +EventTimeline::EventTimeline(size_t frameCount) : Timeline(frameCount, 1) { + PropertyId ids[] = {((PropertyId) Property_Event << 32)}; + setPropertyIds(ids, 1); + _events.setSize(frameCount, NULL); +} + +EventTimeline::~EventTimeline() { + ContainerUtil::cleanUpVectorOfPointers(_events); +} + +void EventTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + if (pEvents == NULL) return; + + Vector &events = *pEvents; + + size_t frameCount = _frames.size(); + + if (lastTime > time) { + // Fire events after last time for looped animations. + apply(skeleton, lastTime, FLT_MAX, pEvents, alpha, blend, direction); + lastTime = -1.0f; + } else if (lastTime >= _frames[frameCount - 1]) { + // Last time is after last i. + return; + } + + if (time < _frames[0]) return;// Time is before first i. + + int i; + if (lastTime < _frames[0]) { + i = 0; + } else { + i = Animation::search(_frames, lastTime) + 1; + float frameTime = _frames[i]; + while (i > 0) { + // Fire multiple events with the same i. + if (_frames[i - 1] != frameTime) break; + i--; + } + } + + for (; (size_t) i < frameCount && time >= _frames[i]; i++) + events.add(_events[i]); +} + +void EventTimeline::setFrame(size_t frame, Event *event) { + _frames[frame] = event->getTime(); + _events[frame] = event; +} + +Vector &EventTimeline::getEvents() { return _events; } diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Extension.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Extension.cpp new file mode 100644 index 0000000..4f47e06 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Extension.cpp @@ -0,0 +1,127 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include + +#include + +using namespace spine; + +SpineExtension *SpineExtension::_instance = NULL; + +void SpineExtension::setInstance(SpineExtension *inValue) { + assert(inValue); + + _instance = inValue; +} + +SpineExtension *SpineExtension::getInstance() { + if (!_instance) _instance = spine::getDefaultExtension(); + assert(_instance); + + return _instance; +} + +SpineExtension::~SpineExtension() { +} + +SpineExtension::SpineExtension() { +} + +DefaultSpineExtension::~DefaultSpineExtension() { +} + +void *DefaultSpineExtension::_alloc(size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + if (size == 0) + return 0; + void *ptr = ::malloc(size); + return ptr; +} + +void *DefaultSpineExtension::_calloc(size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + if (size == 0) + return 0; + + void *ptr = ::malloc(size); + if (ptr) { + memset(ptr, 0, size); + } + return ptr; +} + +void *DefaultSpineExtension::_realloc(void *ptr, size_t size, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + void *mem = NULL; + if (size == 0) + return 0; + if (ptr == NULL) + mem = ::malloc(size); + else + mem = ::realloc(ptr, size); + return mem; +} + +void DefaultSpineExtension::_free(void *mem, const char *file, int line) { + SP_UNUSED(file); + SP_UNUSED(line); + + ::free(mem); +} + +char *DefaultSpineExtension::_readFile(const String &path, int *length) { +#ifndef __EMSCRIPTEN__ + char *data; + FILE *file = fopen(path.buffer(), "rb"); + if (!file) return 0; + + fseek(file, 0, SEEK_END); + *length = (int) ftell(file); + fseek(file, 0, SEEK_SET); + + data = SpineExtension::alloc(*length, __FILE__, __LINE__); + fread(data, 1, *length, file); + fclose(file); + + return data; +#else + return nullptr; +#endif +} + +DefaultSpineExtension::DefaultSpineExtension() : SpineExtension() { +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/IkConstraint.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/IkConstraint.cpp new file mode 100644 index 0000000..8460901 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/IkConstraint.cpp @@ -0,0 +1,379 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(IkConstraint, Updatable) + +void IkConstraint::apply(Bone &bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha) { + Bone *p = bone.getParent(); + float pa = p->_a, pb = p->_b, pc = p->_c, pd = p->_d; + float rotationIK = -bone._ashearX - bone._arotation; + float tx = 0, ty = 0; + + switch (bone._inherit) { + case Inherit_OnlyTranslation: + tx = (targetX - bone._worldX) * MathUtil::sign(bone.getSkeleton().getScaleX()); + ty = (targetY - bone._worldY) * MathUtil::sign(bone.getSkeleton().getScaleY()); + break; + case Inherit_NoRotationOrReflection: { + float s = MathUtil::abs(pa * pd - pb * pc) / MathUtil::max(0.0001f, pa * pa + pc * pc); + float sa = pa / bone._skeleton.getScaleX(); + float sc = pc / bone._skeleton.getScaleY(); + pb = -sc * s * bone._skeleton.getScaleX(); + pd = sa * s * bone._skeleton.getScaleY(); + rotationIK += MathUtil::atan2Deg(sc, sa); + } + default: + float x = targetX - p->_worldX, y = targetY - p->_worldY; + float d = pa * pd - pb * pc; + if (MathUtil::abs(d) <= 0.0001f) { + tx = 0; + ty = 0; + } else { + tx = (x * pd - y * pb) / d - bone._ax; + ty = (y * pa - x * pc) / d - bone._ay; + } + } + rotationIK += MathUtil::atan2Deg(ty, tx); + if (bone._ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) rotationIK -= 360; + else if (rotationIK < -180) + rotationIK += 360; + float sx = bone._ascaleX; + float sy = bone._ascaleY; + if (compress || stretch) { + switch (bone._inherit) { + case Inherit_NoScale: + case Inherit_NoScaleOrReflection: + tx = targetX - bone._worldX; + ty = targetY - bone._worldY; + default:; + } + + float b = bone._data.getLength() * sx; + if (b > 0.0001) { + float dd = tx * tx + ty * ty; + if ((compress && dd < b * b) || (stretch && dd > b * b)) { + float s = (MathUtil::sqrt(dd) / b - 1) * alpha + 1; + sx *= s; + if (uniform) sy *= s; + } + } + } + bone.updateWorldTransform(bone._ax, bone._ay, bone._arotation + rotationIK * alpha, sx, sy, bone._ashearX, + bone._ashearY); +} + +void IkConstraint::apply(Bone &parent, Bone &child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, + float alpha) { + float a, b, c, d; + float px, py, psx, psy, sx, sy; + float cx, cy, csx, cwx, cwy; + int o1, o2, s2, u; + Bone *pp = parent.getParent(); + float tx, ty, dx, dy, dd, l1, l2, a1, a2, r, td, sd, p; + float id, x, y; + if (parent._inherit != Inherit_Normal || child._inherit != Inherit_Normal) return; + px = parent._ax; + py = parent._ay; + psx = parent._ascaleX; + psy = parent._ascaleY; + sx = psx; + sy = psy; + csx = child._ascaleX; + if (psx < 0) { + psx = -psx; + o1 = 180; + s2 = -1; + } else { + o1 = 0; + s2 = 1; + } + if (psy < 0) { + psy = -psy; + s2 = -s2; + } + if (csx < 0) { + csx = -csx; + o2 = 180; + } else + o2 = 0; + r = psx - psy; + cx = child._ax; + u = (r < 0 ? -r : r) <= 0.0001f; + if (!u || stretch) { + cy = 0; + cwx = parent._a * cx + parent._worldX; + cwy = parent._c * cx + parent._worldY; + } else { + cy = child._ay; + cwx = parent._a * cx + parent._b * cy + parent._worldX; + cwy = parent._c * cx + parent._d * cy + parent._worldY; + } + a = pp->_a; + b = pp->_b; + c = pp->_c; + d = pp->_d; + id = a * d - b * c; + id = MathUtil::abs(id) <= 0.0001f ? 0 : 1 / id; + x = cwx - pp->_worldX; + y = cwy - pp->_worldY; + dx = (x * d - y * b) * id - px; + dy = (y * a - x * c) * id - py; + l1 = MathUtil::sqrt(dx * dx + dy * dy); + l2 = child._data.getLength() * csx; + if (l1 < 0.0001) { + apply(parent, targetX, targetY, false, stretch, false, alpha); + child.updateWorldTransform(cx, cy, 0, child._ascaleX, child._ascaleY, child._ashearX, child._ashearY); + return; + } + x = targetX - pp->_worldX; + y = targetY - pp->_worldY; + tx = (x * d - y * b) * id - px; + ty = (y * a - x * c) * id - py; + dd = tx * tx + ty * ty; + if (softness != 0) { + softness *= psx * (csx + 1) * 0.5f; + td = MathUtil::sqrt(dd); + sd = td - l1 - l2 * psx + softness; + if (sd > 0) { + p = MathUtil::min(1.0f, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + if (u) { + float cosine; + l2 *= psx; + cosine = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cosine < -1) { + cosine = -1; + a2 = MathUtil::Pi * bendDir; + } else if (cosine > 1) { + cosine = 1; + a2 = 0; + if (stretch) { + a = (MathUtil::sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + sx *= a; + if (uniform) sy *= a; + } + } else + a2 = MathUtil::acos(cosine) * bendDir; + a = l1 + l2 * cosine; + b = l2 * MathUtil::sin(a2); + a1 = MathUtil::atan2(ty * a - tx * b, tx * a + ty * b); + } else { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, ll = l1 * l1, ta = MathUtil::atan2(ty, tx); + float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c0; + if (d >= 0) { + float q = MathUtil::sqrt(d), r0, r1; + if (c1 < 0) q = -q; + q = -(c1 + q) * 0.5f; + r0 = q / c2; + r1 = c0 / q; + r = MathUtil::abs(r0) < MathUtil::abs(r1) ? r0 : r1; + if (dd - r * r >= 0) { + y = MathUtil::sqrt(dd - r * r) * bendDir; + a1 = ta - MathUtil::atan2(y, r); + a2 = MathUtil::atan2(y / psy, (r - l1) / psx); + goto break_outer; + } + } + { + float minAngle = MathUtil::Pi, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c0 = -a * l1 / (aa - bb); + if (c0 >= -1 && c0 <= 1) { + c0 = MathUtil::acos(c0); + x = a * MathUtil::cos(c0) + l1; + y = b * MathUtil::sin(c0); + d = x * x + y * y; + if (d < minDist) { + minAngle = c0; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) { + maxAngle = c0; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) * 0.5f) { + a1 = ta - MathUtil::atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } else { + a1 = ta - MathUtil::atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + } +break_outer : { + float os = MathUtil::atan2(cy, cx) * s2; + a1 = (a1 - os) * MathUtil::Rad_Deg + o1 - parent._arotation; + if (a1 > 180) a1 -= 360; + else if (a1 < -180) + a1 += 360; + parent.updateWorldTransform(px, py, parent._arotation + a1 * alpha, sx, sy, 0, 0); + a2 = ((a2 + os) * MathUtil::Rad_Deg - child._ashearX) * s2 + o2 - child._arotation; + if (a2 > 180) a2 -= 360; + else if (a2 < -180) + a2 += 360; + child.updateWorldTransform(cx, cy, child._arotation + a2 * alpha, child._ascaleX, child._ascaleY, + child._ashearX, child._ashearY); +} +} + +IkConstraint::IkConstraint(IkConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _bendDirection(data.getBendDirection()), + _compress(data.getCompress()), + _stretch(data.getStretch()), + _mix(data.getMix()), + _softness(data.getSoftness()), + _target(skeleton.findBone( + data.getTarget()->getName())), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); i++) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } +} + +void IkConstraint::update(Physics) { + if (_mix == 0) return; + switch (_bones.size()) { + case 1: { + Bone *bone0 = _bones[0]; + apply(*bone0, _target->getWorldX(), _target->getWorldY(), _compress, _stretch, _data._uniform, _mix); + } break; + case 2: { + Bone *bone0 = _bones[0]; + Bone *bone1 = _bones[1]; + apply(*bone0, *bone1, _target->getWorldX(), _target->getWorldY(), _bendDirection, _stretch, _data._uniform, + _softness, + _mix); + } break; + } +} + +int IkConstraint::getOrder() { + return (int) _data.getOrder(); +} + +IkConstraintData &IkConstraint::getData() { + return _data; +} + +Vector &IkConstraint::getBones() { + return _bones; +} + +Bone *IkConstraint::getTarget() { + return _target; +} + +void IkConstraint::setTarget(Bone *inValue) { + _target = inValue; +} + +int IkConstraint::getBendDirection() { + return _bendDirection; +} + +void IkConstraint::setBendDirection(int inValue) { + _bendDirection = inValue; +} + +float IkConstraint::getMix() { + return _mix; +} + +void IkConstraint::setMix(float inValue) { + _mix = inValue; +} + +bool IkConstraint::getStretch() { + return _stretch; +} + +void IkConstraint::setStretch(bool inValue) { + _stretch = inValue; +} + +bool IkConstraint::getCompress() { + return _compress; +} + +void IkConstraint::setCompress(bool inValue) { + _compress = inValue; +} + +bool IkConstraint::isActive() { + return _active; +} + +void IkConstraint::setActive(bool inValue) { + _active = inValue; +} + +float IkConstraint::getSoftness() { + return _softness; +} + +void IkConstraint::setSoftness(float inValue) { + _softness = inValue; +} + +void IkConstraint::setToSetupPose() { + IkConstraintData &data = this->_data; + this->_mix = data._mix; + this->_softness = data._softness; + this->_bendDirection = data._bendDirection; + this->_compress = data._compress; + this->_stretch = data._stretch; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/IkConstraintData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/IkConstraintData.cpp new file mode 100644 index 0000000..5f232ac --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/IkConstraintData.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +RTTI_IMPL(IkConstraintData, ConstraintData) + +IkConstraintData::IkConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _bendDirection(0), + _compress(false), + _stretch(false), + _uniform(false), + _mix(0), + _softness(0) { +} + +Vector &IkConstraintData::getBones() { + return _bones; +} + +BoneData *IkConstraintData::getTarget() { + return _target; +} + +void IkConstraintData::setTarget(BoneData *inValue) { + _target = inValue; +} + +int IkConstraintData::getBendDirection() { + return _bendDirection; +} + +void IkConstraintData::setBendDirection(int inValue) { + _bendDirection = inValue; +} + +float IkConstraintData::getMix() { + return _mix; +} + +void IkConstraintData::setMix(float inValue) { + _mix = inValue; +} + +bool IkConstraintData::getStretch() { + return _stretch; +} + +void IkConstraintData::setStretch(bool inValue) { + _stretch = inValue; +} + +bool IkConstraintData::getCompress() { + return _compress; +} + +void IkConstraintData::setCompress(bool inValue) { + _compress = inValue; +} + + +bool IkConstraintData::getUniform() { + return _uniform; +} + +void IkConstraintData::setUniform(bool inValue) { + _uniform = inValue; +} + +float IkConstraintData::getSoftness() { + return _softness; +} + +void IkConstraintData::setSoftness(float inValue) { + _softness = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/IkConstraintTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/IkConstraintTimeline.cpp new file mode 100644 index 0000000..8e757f7 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/IkConstraintTimeline.cpp @@ -0,0 +1,141 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(IkConstraintTimeline, CurveTimeline) + +IkConstraintTimeline::IkConstraintTimeline(size_t frameCount, size_t bezierCount, int ikConstraintIndex) + : CurveTimeline(frameCount, IkConstraintTimeline::ENTRIES, bezierCount), _constraintIndex(ikConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_IkConstraint << 32) | ikConstraintIndex}; + setPropertyIds(ids, 1); +} + +void IkConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + IkConstraint *constraintP = skeleton._ikConstraints[_constraintIndex]; + IkConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mix = constraint._data._mix; + constraint._softness = constraint._data._softness; + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + return; + case MixBlend_First: + constraint._mix += (constraint._data._mix - constraint._mix) * alpha; + constraint._softness += (constraint._data._softness - constraint._softness) * alpha; + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + return; + default: + return; + } + } + + float mix = 0, softness = 0; + int i = Animation::search(_frames, time, IkConstraintTimeline::ENTRIES); + int curveType = (int) _curves[i / IkConstraintTimeline::ENTRIES]; + switch (curveType) { + case IkConstraintTimeline::LINEAR: { + float before = _frames[i]; + mix = _frames[i + IkConstraintTimeline::MIX]; + softness = _frames[i + IkConstraintTimeline::SOFTNESS]; + float t = (time - before) / (_frames[i + IkConstraintTimeline::ENTRIES] - before); + mix += (_frames[i + IkConstraintTimeline::ENTRIES + IkConstraintTimeline::MIX] - mix) * t; + softness += (_frames[i + IkConstraintTimeline::ENTRIES + IkConstraintTimeline::SOFTNESS] - softness) * t; + break; + } + case IkConstraintTimeline::STEPPED: { + mix = _frames[i + IkConstraintTimeline::MIX]; + softness = _frames[i + IkConstraintTimeline::SOFTNESS]; + break; + } + default: { + mix = getBezierValue(time, i, IkConstraintTimeline::MIX, curveType - IkConstraintTimeline::BEZIER); + softness = getBezierValue(time, i, IkConstraintTimeline::SOFTNESS, + curveType + IkConstraintTimeline::BEZIER_SIZE - + IkConstraintTimeline::BEZIER); + } + } + + if (blend == MixBlend_Setup) { + constraint._mix = constraint._data._mix + (mix - constraint._data._mix) * alpha; + constraint._softness = constraint._data._softness + (softness - constraint._data._softness) * alpha; + + if (direction == MixDirection_Out) { + constraint._bendDirection = constraint._data._bendDirection; + constraint._compress = constraint._data._compress; + constraint._stretch = constraint._data._stretch; + } else { + constraint._bendDirection = _frames[i + IkConstraintTimeline::BEND_DIRECTION]; + constraint._compress = _frames[i + IkConstraintTimeline::COMPRESS] != 0; + constraint._stretch = _frames[i + IkConstraintTimeline::STRETCH] != 0; + } + } else { + constraint._mix += (mix - constraint._mix) * alpha; + constraint._softness += (softness - constraint._softness) * alpha; + if (direction == MixDirection_In) { + constraint._bendDirection = _frames[i + IkConstraintTimeline::BEND_DIRECTION]; + constraint._compress = _frames[i + IkConstraintTimeline::COMPRESS] != 0; + constraint._stretch = _frames[i + IkConstraintTimeline::STRETCH] != 0; + } + } +} + +void IkConstraintTimeline::setFrame(int frame, float time, float mix, float softness, int bendDirection, bool compress, + bool stretch) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + MIX] = mix; + _frames[frame + SOFTNESS] = softness; + _frames[frame + BEND_DIRECTION] = (float) bendDirection; + _frames[frame + COMPRESS] = compress ? 1 : 0; + _frames[frame + STRETCH] = stretch ? 1 : 0; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/InheritTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/InheritTimeline.cpp new file mode 100644 index 0000000..d8da9e8 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/InheritTimeline.cpp @@ -0,0 +1,81 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(InheritTimeline, Timeline) + +InheritTimeline::InheritTimeline(size_t frameCount, int boneIndex) : Timeline(frameCount, ENTRIES), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_Inherit << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +InheritTimeline::~InheritTimeline() { +} + +void InheritTimeline::setFrame(int frame, float time, Inherit inherit) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + INHERIT] = inherit; +} + + +void InheritTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + SP_UNUSED(alpha); + + Bone *bone = skeleton.getBones()[_boneIndex]; + if (!bone->isActive()) return; + + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) bone->setInherit(bone->_data.getInherit()); + return; + } + + if (time < _frames[0]) { + if (blend == MixBlend_Setup || blend == MixBlend_First) bone->_inherit = bone->_data.getInherit(); + return; + } + int idx = Animation::search(_frames, time, ENTRIES) + INHERIT; + bone->_inherit = static_cast(_frames[idx]); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Json.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Json.cpp new file mode 100644 index 0000000..89d1cc9 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Json.cpp @@ -0,0 +1,559 @@ +/* +Copyright (c) 2009, Dave Gamble +Copyright (c) 2013, Esoteric Software + +Permission is hereby granted, dispose of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +/* Json */ +/* JSON parser in CPP, from json.c in the spine-c runtime */ + +#ifndef _DEFAULT_SOURCE +/* Bring strings.h definitions into string.h, where appropriate */ +#define _DEFAULT_SOURCE +#endif + +#ifndef _BSD_SOURCE +/* Bring strings.h definitions into string.h, where appropriate */ +#define _BSD_SOURCE +#endif + +#include +#include +#include + +#include +#include + +using namespace spine; + +const int Json::JSON_FALSE = 0; +const int Json::JSON_TRUE = 1; +const int Json::JSON_NULL = 2; +const int Json::JSON_NUMBER = 3; +const int Json::JSON_STRING = 4; +const int Json::JSON_ARRAY = 5; +const int Json::JSON_OBJECT = 6; + +const char *Json::_error = NULL; + +Json *Json::getItem(Json *object, const char *string) { + Json *c = object->_child; + while (c && json_strcasecmp(c->_name, string)) { + c = c->_next; + } + return c; +} + +Json *Json::getItem(Json *object, int childIndex) { + Json *current = object->_child; + while (current != NULL && childIndex > 0) { + childIndex--; + current = current->_next; + } + return current; +} + +const char *Json::getString(Json *object, const char *name, const char *defaultValue) { + object = getItem(object, name); + if (object) { + return object->_valueString; + } + + return defaultValue; +} + +float Json::getFloat(Json *value, const char *name, float defaultValue) { + value = getItem(value, name); + return value ? value->_valueFloat : defaultValue; +} + +int Json::getInt(Json *value, const char *name, int defaultValue) { + value = getItem(value, name); + return value ? value->_valueInt : defaultValue; +} + +bool Json::getBoolean(spine::Json *value, const char *name, bool defaultValue) { + value = getItem(value, name); + if (value) { + if (value->_valueString) return strcmp(value->_valueString, "true") == 0; + if (value->_type == JSON_NULL) return false; + if (value->_type == JSON_NUMBER) return value->_valueFloat != 0; + if (value->_type == JSON_FALSE) return false; + if (value->_type == JSON_TRUE) return true; + return defaultValue; + } else { + return defaultValue; + } +} + +const char *Json::getError() { + return _error; +} + +Json::Json(const char *value) : _next(NULL), +#if SPINE_JSON_HAVE_PREV + _prev(NULL), +#endif + _child(NULL), + _type(0), + _size(0), + _valueString(NULL), + _valueInt(0), + _valueFloat(0), + _name(NULL) { + if (value) { + value = parseValue(this, skip(value)); + + assert(value); + } +} + +Json::~Json() { + spine::Json *curr = NULL; + spine::Json *next = _child; + do { + curr = next; + if (curr) { + next = curr->_next; + } + delete curr; + } while (next); + + if (_valueString) { + SpineExtension::free(_valueString, __FILE__, __LINE__); + } + + if (_name) { + SpineExtension::free(_name, __FILE__, __LINE__); + } +} + +const char *Json::skip(const char *inValue) { + if (!inValue) { + /* must propagate NULL since it's often called in skip(f(...)) form */ + return NULL; + } + + while (*inValue && (unsigned char) *inValue <= 32) { + inValue++; + } + + return inValue; +} + +const char *Json::parseValue(Json *item, const char *value) { + /* Referenced by constructor, parseArray(), and parseObject(). */ + /* Always called with the result of skip(). */ +#ifdef SPINE_JSON_DEBUG /* Checked at entry to graph, constructor, and after every parse call. */ + if (!value) { + /* Fail on null. */ + return NULL; + } +#endif + + switch (*value) { + case 'n': { + if (!strncmp(value + 1, "ull", 3)) { + item->_type = JSON_NULL; + return value + 4; + } + break; + } + case 'f': { + if (!strncmp(value + 1, "alse", 4)) { + item->_type = JSON_FALSE; + /* calloc prevents us needing item->_type = JSON_FALSE or valueInt = 0 here */ + return value + 5; + } + break; + } + case 't': { + if (!strncmp(value + 1, "rue", 3)) { + item->_type = JSON_TRUE; + item->_valueInt = 1; + return value + 4; + } + break; + } + case '\"': + return parseString(item, value); + case '[': + return parseArray(item, value); + case '{': + return parseObject(item, value); + case '-': /* fallthrough */ + case '0': /* fallthrough */ + case '1': /* fallthrough */ + case '2': /* fallthrough */ + case '3': /* fallthrough */ + case '4': /* fallthrough */ + case '5': /* fallthrough */ + case '6': /* fallthrough */ + case '7': /* fallthrough */ + case '8': /* fallthrough */ + case '9': + return parseNumber(item, value); + default: + break; + } + + _error = value; + return NULL; /* failure. */ +} + +static const unsigned char firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; + +const char *Json::parseString(Json *item, const char *str) { + const char *ptr = str + 1; + char *ptr2; + char *out; + int len = 0; + unsigned uc, uc2; + if (*str != '\"') { + /* TODO: don't need this check when called from parseValue, but do need from parseObject */ + _error = str; + return 0; + } /* not a string! */ + + while (*ptr != '\"' && *ptr && ++len) { + if (*ptr++ == '\\') { + ptr++; /* Skip escaped quotes. */ + } + } + + out = SpineExtension::alloc(len + 1, __FILE__, __LINE__); /* The length needed for the string, roughly. */ + if (!out) { + return 0; + } + + ptr = str + 1; + ptr2 = out; + while (*ptr != '\"' && *ptr) { + if (*ptr != '\\') { + *ptr2++ = *ptr++; + } else { + ptr++; + switch (*ptr) { + case 'b': + *ptr2++ = '\b'; + break; + case 'f': + *ptr2++ = '\f'; + break; + case 'n': + *ptr2++ = '\n'; + break; + case 'r': + *ptr2++ = '\r'; + break; + case 't': + *ptr2++ = '\t'; + break; + case 'u': { + /* transcode utf16 to utf8. */ + sscanf(ptr + 1, "%4x", &uc); + ptr += 4; /* get the unicode char. */ + + if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) { + break; /* check for invalid. */ + } + + /* TODO provide an option to ignore surrogates, use unicode replacement character? */ + if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ { + if (ptr[1] != '\\' || ptr[2] != 'u') { + break; /* missing second-half of surrogate. */ + } + sscanf(ptr + 3, "%4x", &uc2); + ptr += 6; + if (uc2 < 0xDC00 || uc2 > 0xDFFF) { + break; /* invalid second-half of surrogate. */ + } + uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF)); + } + + len = 4; + if (uc < 0x80) { + len = 1; + } else if (uc < 0x800) { + len = 2; + } else if (uc < 0x10000) { + len = 3; + } + ptr2 += len; + + switch (len) { + case 4: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + /* fallthrough */ + case 3: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + /* fallthrough */ + case 2: + *--ptr2 = ((uc | 0x80) & 0xBF); + uc >>= 6; + /* fallthrough */ + case 1: + *--ptr2 = (uc | firstByteMark[len]); + } + ptr2 += len; + break; + } + default: + *ptr2++ = *ptr; + break; + } + ptr++; + } + } + + *ptr2 = 0; + + if (*ptr == '\"') { + ptr++; /* TODO error handling if not \" or \0 ? */ + } + + item->_valueString = out; + item->_type = JSON_STRING; + + return ptr; +} + +const char *Json::parseNumber(Json *item, const char *num) { + double result = 0.0; + int negative = 0; + char *ptr = (char *) num; + + if (*ptr == '-') { + negative = -1; + ++ptr; + } + + while (*ptr >= '0' && *ptr <= '9') { + result = result * 10.0 + (*ptr - '0'); + ++ptr; + } + + if (*ptr == '.') { + double fraction = 0.0; + int n = 0; + ++ptr; + + while (*ptr >= '0' && *ptr <= '9') { + fraction = (fraction * 10.0) + (*ptr - '0'); + ++ptr; + ++n; + } + result += fraction / pow(10.0, n); + } + + if (negative) { + result = -result; + } + + if (*ptr == 'e' || *ptr == 'E') { + double exponent = 0; + int expNegative = 0; + ++ptr; + + if (*ptr == '-') { + expNegative = -1; + ++ptr; + } else if (*ptr == '+') { + ++ptr; + } + + while (*ptr >= '0' && *ptr <= '9') { + exponent = (exponent * 10.0) + (*ptr - '0'); + ++ptr; + } + + if (expNegative) { + result = result / pow(10, exponent); + } else { + result = result * pow(10, exponent); + } + } + + if (ptr != num) { + /* Parse success, number found. */ + item->_valueFloat = (float) result; + item->_valueInt = (int) result; + item->_type = JSON_NUMBER; + return ptr; + } else { + /* Parse failure, _error is set. */ + _error = num; + return NULL; + } +} + +const char *Json::parseArray(Json *item, const char *value) { + Json *child; + +#ifdef SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */ + if (*value != '[') { + ep = value; + return 0; + } /* not an array! */ +#endif + + item->_type = JSON_ARRAY; + value = skip(value + 1); + if (*value == ']') { + return value + 1; /* empty array. */ + } + + item->_child = child = new (__FILE__, __LINE__) Json(NULL); + if (!item->_child) { + return NULL; /* memory fail */ + } + + value = skip(parseValue(child, skip(value))); /* skip any spacing, get the value. */ + + if (!value) { + return NULL; + } + + item->_size = 1; + + while (*value == ',') { + Json *new_item = new (__FILE__, __LINE__) Json(NULL); + if (!new_item) { + return NULL; /* memory fail */ + } + child->_next = new_item; +#if SPINE_JSON_HAVE_PREV + new_item->prev = child; +#endif + child = new_item; + value = skip(parseValue(child, skip(value + 1))); + if (!value) { + return NULL; /* parse fail */ + } + item->_size++; + } + + if (*value == ']') { + return value + 1; /* end of array */ + } + + _error = value; + + return NULL; /* malformed. */ +} + +/* Build an object from the text. */ +const char *Json::parseObject(Json *item, const char *value) { + Json *child; + +#ifdef SPINE_JSON_DEBUG /* unnecessary, only callsite (parse_value) verifies this */ + if (*value != '{') { + ep = value; + return 0; + } /* not an object! */ +#endif + + item->_type = JSON_OBJECT; + value = skip(value + 1); + if (*value == '}') { + return value + 1; /* empty array. */ + } + + item->_child = child = new (__FILE__, __LINE__) Json(NULL); + if (!item->_child) { + return NULL; + } + value = skip(parseString(child, skip(value))); + if (!value) { + return NULL; + } + child->_name = child->_valueString; + child->_valueString = 0; + if (*value != ':') { + _error = value; + return NULL; + } /* fail! */ + + value = skip(parseValue(child, skip(value + 1))); /* skip any spacing, get the value. */ + if (!value) { + return NULL; + } + + item->_size = 1; + + while (*value == ',') { + Json *new_item = new (__FILE__, __LINE__) Json(NULL); + if (!new_item) { + return NULL; /* memory fail */ + } + child->_next = new_item; +#if SPINE_JSON_HAVE_PREV + new_item->prev = child; +#endif + child = new_item; + value = skip(parseString(child, skip(value + 1))); + if (!value) { + return NULL; + } + child->_name = child->_valueString; + child->_valueString = 0; + if (*value != ':') { + _error = value; + return NULL; + } /* fail! */ + + value = skip(parseValue(child, skip(value + 1))); /* skip any spacing, get the value. */ + if (!value) { + return NULL; + } + item->_size++; + } + + if (*value == '}') { + return value + 1; /* end of array */ + } + + _error = value; + + return NULL; /* malformed. */ +} + +int Json::json_strcasecmp(const char *s1, const char *s2) { + /* TODO we may be able to elide these NULL checks if we can prove + * the graph and input (only callsite is Json_getItem) should not have NULLs + */ + if (s1 && s2) { +#if defined(_WIN32) + return _stricmp(s1, s2); +#else + return strcasecmp(s1, s2); +#endif + } else { + if (s1 < s2) { + return -1; /* s1 is null, s2 is not */ + } else if (s1 == s2) { + return 0; /* both are null */ + } else { + return 1; /* s2 is nul s1 is not */ + } + } +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/LinkedMesh.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/LinkedMesh.cpp new file mode 100644 index 0000000..db10ced --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/LinkedMesh.cpp @@ -0,0 +1,52 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +LinkedMesh::LinkedMesh(MeshAttachment *mesh, const int skinIndex, size_t slotIndex, const String &parent, + bool inheritTimeline) : _mesh(mesh), + _skinIndex(skinIndex), + _skin(""), + _slotIndex(slotIndex), + _parent(parent), + _inheritTimeline(inheritTimeline) { +} + +LinkedMesh::LinkedMesh(MeshAttachment *mesh, const String &skin, size_t slotIndex, const String &parent, + bool inheritTimeline) : _mesh(mesh), + _skinIndex(-1), + _skin(skin), + _slotIndex(slotIndex), + _parent(parent), + _inheritTimeline(inheritTimeline) { +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Log.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Log.cpp new file mode 100644 index 0000000..063141e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Log.cpp @@ -0,0 +1,123 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +void spine::spDebug_printSkeletonData(SkeletonData *skeletonData) { + int i, n; + spDebug_printBoneDatas(skeletonData->getBones()); + + for (i = 0, n = (int) skeletonData->getAnimations().size(); i < n; i++) { + spDebug_printAnimation(skeletonData->getAnimations()[i]); + } +} + +void _spDebug_printTimelineBase(Timeline *timeline) { + printf(" Timeline %s:\n", timeline->getRTTI().getClassName()); + printf(" frame count: %zu\n", timeline->getFrameCount()); + printf(" frame entries: %zu\n", timeline->getFrameEntries()); + printf(" frames: "); + spDebug_printFloats(timeline->getFrames()); + printf("\n"); +} + +void _spDebug_printCurveTimeline(CurveTimeline *timeline) { + _spDebug_printTimelineBase(timeline); + printf(" curves: "); + spDebug_printFloats(timeline->getCurves()); + printf("\n"); +} + +void spine::spDebug_printTimeline(Timeline *timeline) { + if (timeline->getRTTI().instanceOf(CurveTimeline::rtti)) + _spDebug_printCurveTimeline(static_cast(timeline)); + else + _spDebug_printTimelineBase(timeline); +} + +void spine::spDebug_printAnimation(Animation *animation) { + int i, n; + printf("Animation %s: %zu timelines\n", animation->getName().buffer(), animation->getTimelines().size()); + + for (i = 0, n = (int) animation->getTimelines().size(); i < n; i++) { + Timeline *timeline = animation->getTimelines()[i]; + spDebug_printTimeline(timeline); + } +} + +void spine::spDebug_printBoneDatas(Vector &boneDatas) { + int i, n; + for (i = 0, n = (int) boneDatas.size(); i < n; i++) { + spDebug_printBoneData(boneDatas[i]); + } +} + +void spine::spDebug_printBoneData(BoneData *boneData) { + printf("Bone data %s: %f, %f, %f, %f, %f, %f %f\n", boneData->getName().buffer(), boneData->getRotation(), + boneData->getScaleX(), boneData->getScaleY(), boneData->getX(), boneData->getY(), boneData->getShearX(), + boneData->getShearY()); +} + +void spine::spDebug_printSkeleton(Skeleton *skeleton) { + spDebug_printBones(skeleton->getBones()); +} + +void spine::spDebug_printBones(Vector &bones) { + int i, n; + for (i = 0, n = (int) bones.size(); i < n; i++) { + spDebug_printBone(bones[i]); + } +} + +void spine::spDebug_printBone(Bone *bone) { + printf("Bone %s: %f, %f, %f, %f, %f, %f\n", bone->getData().getName().buffer(), bone->getA(), bone->getB(), + bone->getC(), bone->getD(), bone->getWorldX(), bone->getWorldY()); +} + +void spine::spDebug_printFloats(float *values, int numFloats) { + int i; + printf("(%i) [", numFloats); + for (i = 0; i < numFloats; i++) { + printf("%f, ", values[i]); + } + printf("]"); +} + +void spine::spDebug_printFloats(Vector &values) { + int i, n; + printf("(%zu) [", values.size()); + for (i = 0, n = (int) values.size(); i < n; i++) { + printf("%f, ", values[i]); + } + printf("]"); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/MathUtil.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/MathUtil.cpp new file mode 100644 index 0000000..44f48fa --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/MathUtil.cpp @@ -0,0 +1,132 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include +#include + +// Required for division by 0 in _isNaN on MSVC +#ifdef _MSC_VER +#pragma warning(disable : 4723) +#endif + +using namespace spine; + +const float MathUtil::Pi = 3.1415926535897932385f; +const float MathUtil::Pi_2 = 3.1415926535897932385f * 2; +const float MathUtil::InvPi_2 = 1 / MathUtil::Pi_2; +const float MathUtil::Deg_Rad = (3.1415926535897932385f / 180.0f); +const float MathUtil::Rad_Deg = (180.0f / 3.1415926535897932385f); + +float MathUtil::abs(float v) { + return ((v) < 0 ? -(v) : (v)); +} + +float MathUtil::sign(float v) { + return ((v) < 0 ? -1.0f : (v) > 0 ? 1.0f + : 0.0f); +} + +float MathUtil::clamp(float x, float min, float max) { + return ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))); +} + +float MathUtil::fmod(float a, float b) { + return (float) ::fmod(a, b); +} + +/// Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323 +/// degrees), largest error of 0.00488 radians (0.2796 degrees). +float MathUtil::atan2(float y, float x) { + return (float) ::atan2(y, x); +} + +float MathUtil::atan2Deg(float y, float x) { + return MathUtil::atan2(y, x) * MathUtil::Rad_Deg; +} + +/// Returns the cosine in radians from a lookup table. +float MathUtil::cos(float radians) { + return (float) ::cos(radians); +} + +/// Returns the sine in radians from a lookup table. +float MathUtil::sin(float radians) { + return (float) ::sin(radians); +} + +float MathUtil::sqrt(float v) { + return (float) ::sqrt(v); +} + +float MathUtil::acos(float v) { + return (float) ::acos(v); +} + +/// Returns the sine in radians from a lookup table. +float MathUtil::sinDeg(float degrees) { + return (float) ::sin(degrees * MathUtil::Deg_Rad); +} + +/// Returns the cosine in radians from a lookup table. +float MathUtil::cosDeg(float degrees) { + return (float) ::cos(degrees * MathUtil::Deg_Rad); +} + +bool MathUtil::isNan(float v) { + return std::isnan(v); +} + +float MathUtil::quietNan() { + return std::nan(""); +} + +float MathUtil::random() { + return ::rand() / (float) RAND_MAX; +} + +float MathUtil::randomTriangular(float min, float max) { + return randomTriangular(min, max, (min + max) * 0.5f); +} + +float MathUtil::randomTriangular(float min, float max, float mode) { + float u = random(); + float d = max - min; + if (u <= (mode - min) / d) return min + sqrt(u * d * (mode - min)); + return max - sqrt((1 - u) * d * (max - mode)); +} + +float MathUtil::pow(float a, float b) { + return (float) ::pow(a, b); +} + +float MathUtil::ceil(float v) { + return ::ceil(v); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/MeshAttachment.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/MeshAttachment.cpp new file mode 100644 index 0000000..e40bc6e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/MeshAttachment.cpp @@ -0,0 +1,241 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL(MeshAttachment, VertexAttachment) + +MeshAttachment::MeshAttachment(const String &name) : VertexAttachment(name), + _parentMesh(NULL), + _path(), + _color(1, 1, 1, 1), + _hullLength(0), + _width(0), + _height(0), + _region(NULL), + _sequence(NULL) {} + +MeshAttachment::~MeshAttachment() { + if (_sequence) delete _sequence; +} + +void MeshAttachment::updateRegion() { + if (_uvs.size() != _regionUVs.size()) { + _uvs.setSize(_regionUVs.size(), 0); + } + + if (_region == nullptr) { + return; + } + + int i = 0, n = (int) _regionUVs.size(); + float u = _region->u, v = _region->v; + float width = 0, height = 0; + switch (_region->degrees) { + case 90: { + float textureWidth = _region->height / (_region->u2 - _region->u); + float textureHeight = _region->width / (_region->v2 - _region->v); + u -= (_region->originalHeight - _region->offsetY - _region->height) / textureWidth; + v -= (_region->originalWidth - _region->offsetX - _region->width) / textureHeight; + width = _region->originalHeight / textureWidth; + height = _region->originalWidth / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + _regionUVs[i + 1] * width; + _uvs[i + 1] = v + (1 - _regionUVs[i]) * height; + } + return; + } + case 180: { + float textureWidth = _region->width / (_region->u2 - _region->u); + float textureHeight = _region->height / (_region->v2 - _region->v); + u -= (_region->originalWidth - _region->offsetX - _region->width) / textureWidth; + v -= _region->offsetY / textureHeight; + width = _region->originalWidth / textureWidth; + height = _region->originalHeight / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + (1 - _regionUVs[i]) * width; + _uvs[i + 1] = v + (1 - _regionUVs[i + 1]) * height; + } + return; + } + case 270: { + float textureHeight = _region->height / (_region->v2 - _region->v); + float textureWidth = _region->width / (_region->u2 - _region->u); + u -= _region->offsetY / textureWidth; + v -= _region->offsetX / textureHeight; + width = _region->originalHeight / textureWidth; + height = _region->originalWidth / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + (1 - _regionUVs[i + 1]) * width; + _uvs[i + 1] = v + _regionUVs[i] * height; + } + return; + } + default: { + float textureWidth = _region->width / (_region->u2 - _region->u); + float textureHeight = _region->height / (_region->v2 - _region->v); + u -= _region->offsetX / textureWidth; + v -= (_region->originalHeight - _region->offsetY - _region->height) / textureHeight; + width = _region->originalWidth / textureWidth; + height = _region->originalHeight / textureHeight; + for (i = 0; i < n; i += 2) { + _uvs[i] = u + _regionUVs[i] * width; + _uvs[i + 1] = v + _regionUVs[i + 1] * height; + } + } + } +} + +int MeshAttachment::getHullLength() { + return _hullLength; +} + +void MeshAttachment::setHullLength(int inValue) { + _hullLength = inValue; +} + +Vector &MeshAttachment::getRegionUVs() { + return _regionUVs; +} + +Vector &MeshAttachment::getUVs() { + return _uvs; +} + +Vector &MeshAttachment::getTriangles() { + return _triangles; +} + +const String &MeshAttachment::getPath() { + return _path; +} + +void MeshAttachment::setPath(const String &inValue) { + _path = inValue; +} + +TextureRegion *MeshAttachment::getRegion() { + return _region; +} + +void MeshAttachment::setRegion(TextureRegion *region) { + _region = region; +} + +Sequence *MeshAttachment::getSequence() { + return _sequence; +} + +void MeshAttachment::setSequence(Sequence *sequence) { + _sequence = sequence; +} + +MeshAttachment *MeshAttachment::getParentMesh() { + return _parentMesh; +} + +void MeshAttachment::setParentMesh(MeshAttachment *inValue) { + _parentMesh = inValue; + if (inValue != NULL) { + _bones.clearAndAddAll(inValue->_bones); + _vertices.clearAndAddAll(inValue->_vertices); + _worldVerticesLength = inValue->_worldVerticesLength; + _regionUVs.clearAndAddAll(inValue->_regionUVs); + _triangles.clearAndAddAll(inValue->_triangles); + _hullLength = inValue->_hullLength; + _edges.clearAndAddAll(inValue->_edges); + _width = inValue->_width; + _height = inValue->_height; + } +} + +Vector &MeshAttachment::getEdges() { + return _edges; +} + +float MeshAttachment::getWidth() { + return _width; +} + +void MeshAttachment::setWidth(float inValue) { + _width = inValue; +} + +float MeshAttachment::getHeight() { + return _height; +} + +void MeshAttachment::setHeight(float inValue) { + _height = inValue; +} + +spine::Color &MeshAttachment::getColor() { + return _color; +} + +Attachment *MeshAttachment::copy() { + if (_parentMesh) return newLinkedMesh(); + + MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName()); + copy->setRegion(_region); + copy->setSequence(_sequence != NULL ? _sequence->copy() : NULL); + copy->_path = _path; + copy->_color.set(_color); + + copyTo(copy); + copy->_regionUVs.clearAndAddAll(_regionUVs); + copy->_uvs.clearAndAddAll(_uvs); + copy->_triangles.clearAndAddAll(_triangles); + copy->_hullLength = _hullLength; + + // Nonessential. + copy->_edges.clearAndAddAll(copy->_edges); + copy->_width = _width; + copy->_height = _height; + return copy; +} + +MeshAttachment *MeshAttachment::newLinkedMesh() { + MeshAttachment *copy = new (__FILE__, __LINE__) MeshAttachment(getName()); + copy->setRegion(_region); + copy->_path = _path; + copy->_color.set(_color); + copy->_timelineAttachment = this->_timelineAttachment; + copy->setParentMesh(_parentMesh ? _parentMesh : this); + if (copy->_region) copy->updateRegion(); + return copy; +} + +void MeshAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride) { + if (_sequence) _sequence->apply(&slot, this); + VertexAttachment::computeWorldVertices(slot, start, count, worldVertices, offset, stride); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathAttachment.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathAttachment.cpp new file mode 100644 index 0000000..5a5670f --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathAttachment.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL(PathAttachment, VertexAttachment) + +PathAttachment::PathAttachment(const String &name) : VertexAttachment(name), _closed(false), _constantSpeed(false), + _color() { +} + +Vector &PathAttachment::getLengths() { + return _lengths; +} + +bool PathAttachment::isClosed() { + return _closed; +} + +void PathAttachment::setClosed(bool inValue) { + _closed = inValue; +} + +bool PathAttachment::isConstantSpeed() { + return _constantSpeed; +} + +void PathAttachment::setConstantSpeed(bool inValue) { + _constantSpeed = inValue; +} + +Color &PathAttachment::getColor() { + return _color; +} + +Attachment *PathAttachment::copy() { + PathAttachment *copy = new (__FILE__, __LINE__) PathAttachment(getName()); + copyTo(copy); + copy->_lengths.clearAndAddAll(_lengths); + copy->_closed = _closed; + copy->_constantSpeed = _constantSpeed; + return copy; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraint.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraint.cpp new file mode 100644 index 0000000..fb8683e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraint.cpp @@ -0,0 +1,588 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraint, Updatable) + +const float PathConstraint::EPSILON = 0.00001f; +const int PathConstraint::NONE = -1; +const int PathConstraint::BEFORE = -2; +const int PathConstraint::AFTER = -3; + +PathConstraint::PathConstraint(PathConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _target(skeleton.findSlot( + data.getTarget()->getName())), + _position(data.getPosition()), + _spacing(data.getSpacing()), + _mixRotate(data.getMixRotate()), + _mixX(data.getMixX()), + _mixY(data.getMixY()), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); i++) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } + + _segments.setSize(10, 0); +} + +void PathConstraint::update(Physics) { + Attachment *baseAttachment = _target->getAttachment(); + if (baseAttachment == NULL || !baseAttachment->getRTTI().instanceOf(PathAttachment::rtti)) { + return; + } + PathAttachment *attachment = static_cast(baseAttachment); + + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY; + if (mixRotate == 0 && mixX == 0 && mixY == 0) return; + + PathConstraintData &data = _data; + bool tangents = data._rotateMode == RotateMode_Tangent, scale = data._rotateMode == RotateMode_ChainScale; + size_t boneCount = _bones.size(); + size_t spacesCount = tangents ? boneCount : boneCount + 1; + _spaces.setSize(spacesCount, 0); + if (scale) _lengths.setSize(boneCount, 0); + float spacing = _spacing; + + switch (data._spacingMode) { + case SpacingMode_Percent: { + if (scale) { + for (size_t i = 0, n = spacesCount - 1; i < n; i++) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.getLength(); + float x = setupLength * bone._a; + float y = setupLength * bone._c; + _lengths[i] = MathUtil::sqrt(x * x + y * y); + } + } + for (size_t i = 1; i < spacesCount; ++i) { + _spaces[i] = spacing; + } + break; + } + case SpacingMode_Proportional: { + float sum = 0; + for (size_t i = 0, n = spacesCount - 1; i < n;) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.getLength(); + if (setupLength < PathConstraint::EPSILON) { + if (scale) _lengths[i] = 0; + _spaces[++i] = spacing; + } else { + float x = setupLength * bone._a, y = setupLength * bone._c; + float length = MathUtil::sqrt(x * x + y * y); + if (scale) _lengths[i] = length; + _spaces[++i] = length; + sum += length; + } + } + if (sum > 0) { + sum = spacesCount / sum * spacing; + for (size_t i = 1; i < spacesCount; i++) { + _spaces[i] *= sum; + } + } + break; + } + default: { + bool lengthSpacing = data._spacingMode == SpacingMode_Length; + for (size_t i = 0, n = spacesCount - 1; i < n;) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + float setupLength = bone._data.getLength(); + if (setupLength < PathConstraint::EPSILON) { + if (scale) _lengths[i] = 0; + _spaces[++i] = spacing; + } else { + float x = setupLength * bone._a, y = setupLength * bone._c; + float length = MathUtil::sqrt(x * x + y * y); + if (scale) _lengths[i] = length; + _spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + } + } + + Vector &positions = computeWorldPositions(*attachment, (int) spacesCount, tangents); + float boneX = positions[0]; + float boneY = positions[1]; + float offsetRotation = data.getOffsetRotation(); + bool tip; + if (offsetRotation == 0) { + tip = data._rotateMode == RotateMode_Chain; + } else { + tip = false; + Bone &p = _target->getBone(); + offsetRotation *= p.getA() * p.getD() - p.getB() * p.getC() > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + } + + for (size_t i = 0, p = 3; i < boneCount; i++, p += 3) { + Bone *boneP = _bones[i]; + Bone &bone = *boneP; + bone._worldX += (boneX - bone._worldX) * mixX; + bone._worldY += (boneY - bone._worldY) * mixY; + float x = positions[p]; + float y = positions[p + 1]; + float dx = x - boneX; + float dy = y - boneY; + if (scale) { + float length = _lengths[i]; + if (length >= PathConstraint::EPSILON) { + float s = (MathUtil::sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1; + bone._a *= s; + bone._c *= s; + } + } + + boneX = x; + boneY = y; + + if (mixRotate > 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d, r, cos, sin; + if (tangents) + r = positions[p - 1]; + else if (_spaces[i + 1] < PathConstraint::EPSILON) + r = positions[p + 2]; + else + r = MathUtil::atan2(dy, dx); + + r -= MathUtil::atan2(c, a); + + if (tip) { + cos = MathUtil::cos(r); + sin = MathUtil::sin(r); + float length = bone._data.getLength(); + boneX += (length * (cos * a - sin * c) - dx) * mixRotate; + boneY += (length * (sin * a + cos * c) - dy) * mixRotate; + } else + r += offsetRotation; + + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= mixRotate; + cos = MathUtil::cos(r); + sin = MathUtil::sin(r); + bone._a = cos * a - sin * c; + bone._b = cos * b - sin * d; + bone._c = sin * a + cos * c; + bone._d = sin * b + cos * d; + } + + bone.updateAppliedTransform(); + } +} + +int PathConstraint::getOrder() { + return (int) _data.getOrder(); +} + +float PathConstraint::getPosition() { + return _position; +} + +void PathConstraint::setPosition(float inValue) { + _position = inValue; +} + +float PathConstraint::getSpacing() { + return _spacing; +} + +void PathConstraint::setSpacing(float inValue) { + _spacing = inValue; +} + +float PathConstraint::getMixRotate() { + return _mixRotate; +} + +void PathConstraint::setMixRotate(float inValue) { + _mixRotate = inValue; +} + +float PathConstraint::getMixX() { + return _mixX; +} + +void PathConstraint::setMixX(float inValue) { + _mixX = inValue; +} + +float PathConstraint::getMixY() { + return _mixY; +} + +void PathConstraint::setMixY(float inValue) { + _mixY = inValue; +} + +Vector &PathConstraint::getBones() { + return _bones; +} + +Slot *PathConstraint::getTarget() { + return _target; +} + +void PathConstraint::setTarget(Slot *inValue) { + _target = inValue; +} + +PathConstraintData &PathConstraint::getData() { + return _data; +} + +Vector & +PathConstraint::computeWorldPositions(PathAttachment &path, int spacesCount, bool tangents) { + Slot &target = *_target; + float position = _position; + _positions.setSize(spacesCount * 3 + 2, 0); + Vector &out = _positions; + Vector &world = _world; + bool closed = path.isClosed(); + int verticesLength = (int) path.getWorldVerticesLength(); + int curveCount = verticesLength / 6; + int prevCurve = NONE; + + float pathLength; + if (!path.isConstantSpeed()) { + Vector &lengths = path.getLengths(); + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (_data._positionMode == PositionMode_Percent) position *= pathLength; + + float multiplier = 0; + switch (_data._spacingMode) { + case SpacingMode_Percent: + multiplier = pathLength; + break; + case SpacingMode_Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + } + + world.setSize(8, 0); + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { + float space = _spaces[i] * multiplier; + position += space; + float p = position; + + if (closed) { + p = MathUtil::fmod(p, pathLength); + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + if (prevCurve != BEFORE) { + prevCurve = BEFORE; + path.computeWorldVertices(target, 2, 4, world, 0); + } + + addBeforePosition(p, world, 0, out, o); + + continue; + } else if (p > pathLength) { + if (prevCurve != AFTER) { + prevCurve = AFTER; + path.computeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + + addAfterPosition(p - pathLength, world, 0, out, o); + + continue; + } + + // Determine curve containing position. + for (;; curve++) { + float length = lengths[curve]; + if (p > length) continue; + + if (curve == 0) + p /= length; + else { + float prev = lengths[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + if (curve != prevCurve) { + prevCurve = curve; + if (closed && curve == curveCount) { + path.computeWorldVertices(target, verticesLength - 4, 4, world, 0); + path.computeWorldVertices(target, 0, 4, world, 4); + } else + path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0); + } + + addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], + out, o, tangents || (i > 0 && space < EPSILON)); + } + return out; + } + + // World vertices. + if (closed) { + verticesLength += 2; + world.setSize(verticesLength, 0); + path.computeWorldVertices(target, 2, verticesLength - 4, world, 0); + path.computeWorldVertices(target, 0, 2, world, verticesLength - 4); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } else { + curveCount--; + verticesLength -= 4; + world.setSize(verticesLength, 0); + path.computeWorldVertices(target, 2, verticesLength, world, 0); + } + + // Curve lengths. + _curves.setSize(curveCount, 0); + pathLength = 0; + float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0; + float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy; + for (int i = 0, w = 2; i < curveCount; i++, w += 6) { + cx1 = world[w]; + cy1 = world[w + 1]; + cx2 = world[w + 2]; + cy2 = world[w + 3]; + x2 = world[w + 4]; + y2 = world[w + 5]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f; + tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f; + dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + + if (_data._positionMode == PositionMode_Percent) position *= pathLength; + + float multiplier = 0; + switch (_data._spacingMode) { + case SpacingMode_Percent: + multiplier = pathLength; + break; + case SpacingMode_Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + } + + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { + float space = _spaces[i] * multiplier; + position += space; + float p = position; + + if (closed) { + p = MathUtil::fmod(p, pathLength); + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + addBeforePosition(p, world, 0, out, o); + continue; + } else if (p > pathLength) { + addAfterPosition(p - pathLength, world, verticesLength - 4, out, o); + continue; + } + + // Determine curve containing position. + for (;; curve++) { + float length = _curves[curve]; + if (p > length) continue; + if (curve == 0) + p /= length; + else { + float prev = _curves[curve - 1]; + p = (p - prev) / (length - prev); + } + break; + } + + // Curve segment lengths. + if (curve != prevCurve) { + prevCurve = curve; + int ii = curve * 6; + x1 = world[ii]; + y1 = world[ii + 1]; + cx1 = world[ii + 2]; + cy1 = world[ii + 3]; + cx2 = world[ii + 4]; + cy2 = world[ii + 5]; + x2 = world[ii + 6]; + y2 = world[ii + 7]; + tmpx = (x1 - cx1 * 2 + cx2) * 0.03f; + tmpy = (y1 - cy1 * 2 + cy2) * 0.03f; + dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f; + dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f; + ddfx = tmpx * 2 + dddfx; + ddfy = tmpy * 2 + dddfy; + dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f; + dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f; + curveLength = MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += MathUtil::sqrt(dfx * dfx + dfy * dfy); + _segments[9] = curveLength; + segment = 0; + } + + // Weight by segment length. + p *= curveLength; + for (;; segment++) { + float length = _segments[segment]; + if (p > length) continue; + if (segment == 0) + p /= length; + else { + float prev = _segments[segment - 1]; + p = segment + (p - prev) / (length - prev); + } + break; + } + addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, + tangents || (i > 0 && space < EPSILON)); + } + + return out; +} + +void PathConstraint::addBeforePosition(float p, Vector &temp, int i, Vector &output, int o) { + float x1 = temp[i]; + float y1 = temp[i + 1]; + float dx = temp[i + 2] - x1; + float dy = temp[i + 3] - y1; + float r = MathUtil::atan2(dy, dx); + output[o] = x1 + p * MathUtil::cos(r); + output[o + 1] = y1 + p * MathUtil::sin(r); + output[o + 2] = r; +} + +void PathConstraint::addAfterPosition(float p, Vector &temp, int i, Vector &output, int o) { + float x1 = temp[i + 2]; + float y1 = temp[i + 3]; + float dx = x1 - temp[i]; + float dy = y1 - temp[i + 1]; + float r = MathUtil::atan2(dy, dx); + output[o] = x1 + p * MathUtil::cos(r); + output[o + 1] = y1 + p * MathUtil::sin(r); + output[o + 2] = r; +} + +void PathConstraint::addCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, + float y2, Vector &output, int o, bool tangents) { + if (p < EPSILON || MathUtil::isNan(p)) { + output[o] = x1; + output[o + 1] = y1; + output[o + 2] = MathUtil::atan2(cy1 - y1, cx1 - x1); + return; + } + + float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u; + float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p; + float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt; + output[o] = x; + output[o + 1] = y; + if (tangents) { + if (p < 0.001) + output[o + 2] = MathUtil::atan2(cy1 - y1, cx1 - x1); + else + output[o + 2] = MathUtil::atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), + x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } +} + +bool PathConstraint::isActive() { + return _active; +} + +void PathConstraint::setActive(bool inValue) { + _active = inValue; +} + +void PathConstraint::setToSetupPose() { + PathConstraintData &data = this->_data; + this->_position = data._position; + this->_spacing = data._spacing; + this->_mixRotate = data._mixRotate; + this->_mixX = data._mixX; + this->_mixY = data._mixY; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintData.cpp new file mode 100644 index 0000000..6d13223 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintData.cpp @@ -0,0 +1,136 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintData, ConstraintData) + +PathConstraintData::PathConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _positionMode(PositionMode_Fixed), + _spacingMode(SpacingMode_Length), + _rotateMode(RotateMode_Tangent), + _offsetRotation(0), + _position(0), + _spacing(0), + _mixRotate(0), + _mixX(0), + _mixY(0) { +} + +Vector &PathConstraintData::getBones() { + return _bones; +} + +SlotData *PathConstraintData::getTarget() { + return _target; +} + +void PathConstraintData::setTarget(SlotData *inValue) { + _target = inValue; +} + +PositionMode PathConstraintData::getPositionMode() { + return _positionMode; +} + +void PathConstraintData::setPositionMode(PositionMode inValue) { + _positionMode = inValue; +} + +SpacingMode PathConstraintData::getSpacingMode() { + return _spacingMode; +} + +void PathConstraintData::setSpacingMode(SpacingMode inValue) { + _spacingMode = inValue; +} + +RotateMode PathConstraintData::getRotateMode() { + return _rotateMode; +} + +void PathConstraintData::setRotateMode(RotateMode inValue) { + _rotateMode = inValue; +} + +float PathConstraintData::getOffsetRotation() { + return _offsetRotation; +} + +void PathConstraintData::setOffsetRotation(float inValue) { + _offsetRotation = inValue; +} + +float PathConstraintData::getPosition() { + return _position; +} + +void PathConstraintData::setPosition(float inValue) { + _position = inValue; +} + +float PathConstraintData::getSpacing() { + return _spacing; +} + +void PathConstraintData::setSpacing(float inValue) { + _spacing = inValue; +} + +float PathConstraintData::getMixRotate() { + return _mixRotate; +} + +void PathConstraintData::setMixRotate(float inValue) { + _mixRotate = inValue; +} + +float PathConstraintData::getMixX() { + return _mixX; +} + +void PathConstraintData::setMixX(float inValue) { + _mixX = inValue; +} + +float PathConstraintData::getMixY() { + return _mixY; +} + +void PathConstraintData::setMixY(float inValue) { + _mixY = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintMixTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintMixTimeline.cpp new file mode 100644 index 0000000..9d4969c --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintMixTimeline.cpp @@ -0,0 +1,126 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintMixTimeline, CurveTimeline) + +PathConstraintMixTimeline::PathConstraintMixTimeline(size_t frameCount, size_t bezierCount, int pathConstraintIndex) + : CurveTimeline(frameCount, PathConstraintMixTimeline::ENTRIES, bezierCount), + _constraintIndex(pathConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_PathConstraintMix << 32) | pathConstraintIndex}; + setPropertyIds(ids, 1); +} + +void PathConstraintMixTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraintP = skeleton._pathConstraints[_constraintIndex]; + PathConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mixRotate = constraint._data._mixRotate; + constraint._mixX = constraint._data._mixX; + constraint._mixY = constraint._data._mixY; + return; + case MixBlend_First: + constraint._mixRotate += (constraint._data._mixRotate - constraint._mixRotate) * alpha; + constraint._mixX += (constraint._data._mixX - constraint._mixX) * alpha; + constraint._mixY += (constraint._data._mixY - constraint._mixY) * alpha; + default: { + } + } + return; + } + + float rotate, x, y; + int i = Animation::search(_frames, time, PathConstraintMixTimeline::ENTRIES); + int curveType = (int) _curves[i >> 2]; + switch (curveType) { + case LINEAR: { + float before = _frames[i]; + rotate = _frames[i + ROTATE]; + x = _frames[i + X]; + y = _frames[i + Y]; + float t = (time - before) / (_frames[i + ENTRIES] - before); + rotate += (_frames[i + ENTRIES + ROTATE] - rotate) * t; + x += (_frames[i + ENTRIES + X] - x) * t; + y += (_frames[i + ENTRIES + Y] - y) * t; + break; + } + case STEPPED: { + rotate = _frames[i + ROTATE]; + x = _frames[i + X]; + y = _frames[i + Y]; + break; + } + default: { + rotate = getBezierValue(time, i, ROTATE, curveType - BEZIER); + x = getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER); + y = getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER); + } + } + + if (blend == MixBlend_Setup) { + PathConstraintData data = constraint._data; + constraint._mixRotate = data._mixRotate + (rotate - data._mixRotate) * alpha; + constraint._mixX = data._mixX + (x - data._mixX) * alpha; + constraint._mixY = data._mixY + (y - data._mixY) * alpha; + } else { + constraint._mixRotate += (rotate - constraint._mixRotate) * alpha; + constraint._mixX += (x - constraint._mixX) * alpha; + constraint._mixY += (y - constraint._mixY) * alpha; + } +} + +void PathConstraintMixTimeline::setFrame(int frame, float time, float mixRotate, float mixX, float mixY) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + ROTATE] = mixRotate; + _frames[frame + X] = mixX; + _frames[frame + Y] = mixY; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp new file mode 100644 index 0000000..25ab8d4 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintPositionTimeline.cpp @@ -0,0 +1,66 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintPositionTimeline, CurveTimeline1) + +PathConstraintPositionTimeline::PathConstraintPositionTimeline(size_t frameCount, size_t bezierCount, + int pathConstraintIndex) : CurveTimeline1(frameCount, + bezierCount), + _constraintIndex( + pathConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_PathConstraintPosition << 32) | pathConstraintIndex}; + setPropertyIds(ids, 1); +} + +PathConstraintPositionTimeline::~PathConstraintPositionTimeline() { +} + +void PathConstraintPositionTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraint = skeleton._pathConstraints[_constraintIndex]; + if (constraint->_active) constraint->_position = getAbsoluteValue(time, alpha, blend, constraint->_position, constraint->_data._position); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp new file mode 100644 index 0000000..e1921dd --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PathConstraintSpacingTimeline.cpp @@ -0,0 +1,64 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PathConstraintSpacingTimeline, PathConstraintPositionTimeline) + +PathConstraintSpacingTimeline::PathConstraintSpacingTimeline(size_t frameCount, size_t bezierCount, + int pathConstraintIndex) : CurveTimeline1(frameCount, + bezierCount), + _pathConstraintIndex( + pathConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_PathConstraintSpacing << 32) | pathConstraintIndex}; + setPropertyIds(ids, 1); +} + +void PathConstraintSpacingTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + PathConstraint *constraint = skeleton._pathConstraints[_pathConstraintIndex]; + if (constraint->_active) + constraint->_spacing = getAbsoluteValue(time, alpha, blend, constraint->_spacing, constraint->_data._spacing); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PhysicsConstraint.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PhysicsConstraint.cpp new file mode 100644 index 0000000..adf9d8f --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PhysicsConstraint.cpp @@ -0,0 +1,493 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PhysicsConstraint, Updatable) + +PhysicsConstraint::PhysicsConstraint(PhysicsConstraintData &data, Skeleton &skeleton) + : _data(data), _skeleton(skeleton) { + _bone = skeleton.getBones()[data.getBone()->getIndex()]; + _inertia = data.getInertia(); + _strength = data.getStrength(); + _damping = data.getDamping(); + _massInverse = data.getMassInverse(); + _wind = data.getWind(); + _gravity = data.getGravity(); + _mix = data.getMix(); + + _reset = true; + _ux = 0; + _uy = 0; + _cx = 0; + _cy = 0; + _tx = 0; + _ty = 0; + _xOffset = 0; + _xVelocity = 0; + _yOffset = 0; + _yVelocity = 0; + _rotateOffset = 0; + _rotateVelocity = 0; + _scaleOffset = 0; + _scaleVelocity = 0; + _active = false; + _remaining = 0; + _lastTime = 0; +} + +PhysicsConstraintData &PhysicsConstraint::getData() { + return _data; +} + +void PhysicsConstraint::setBone(Bone *bone) { + _bone = bone; +} + +Bone *PhysicsConstraint::getBone() { + return _bone; +} + +void PhysicsConstraint::setInertia(float value) { + _inertia = value; +} + +float PhysicsConstraint::getInertia() { + return _inertia; +} + +void PhysicsConstraint::setStrength(float value) { + _strength = value; +} + +float PhysicsConstraint::getStrength() { + return _strength; +} + +void PhysicsConstraint::setDamping(float value) { + _damping = value; +} + +float PhysicsConstraint::getDamping() { + return _damping; +} + +void PhysicsConstraint::setMassInverse(float value) { + _massInverse = value; +} + +float PhysicsConstraint::getMassInverse() { + return _massInverse; +} + +void PhysicsConstraint::setWind(float value) { + _wind = value; +} + +float PhysicsConstraint::getWind() { + return _wind; +} + +void PhysicsConstraint::setGravity(float value) { + _gravity = value; +} + +float PhysicsConstraint::getGravity() { + return _gravity; +} + +void PhysicsConstraint::setMix(float value) { + _mix = value; +} + +float PhysicsConstraint::getMix() { + return _mix; +} + +void PhysicsConstraint::setReset(bool value) { + _reset = value; +} + +bool PhysicsConstraint::getReset() { + return _reset; +} + +void PhysicsConstraint::setUx(float value) { + _ux = value; +} + +float PhysicsConstraint::getUx() { + return _ux; +} + +void PhysicsConstraint::setUy(float value) { + _uy = value; +} + +float PhysicsConstraint::getUy() { + return _uy; +} + +void PhysicsConstraint::setCx(float value) { + _cx = value; +} + +float PhysicsConstraint::getCx() { + return _cx; +} + +void PhysicsConstraint::setCy(float value) { + _cy = value; +} + +float PhysicsConstraint::getCy() { + return _cy; +} + +void PhysicsConstraint::setTx(float value) { + _tx = value; +} + +float PhysicsConstraint::getTx() { + return _tx; +} + +void PhysicsConstraint::setTy(float value) { + _ty = value; +} + +float PhysicsConstraint::getTy() { + return _ty; +} + +void PhysicsConstraint::setXOffset(float value) { + _xOffset = value; +} + +float PhysicsConstraint::getXOffset() { + return _xOffset; +} + +void PhysicsConstraint::setXVelocity(float value) { + _xVelocity = value; +} + +float PhysicsConstraint::getXVelocity() { + return _xVelocity; +} + +void PhysicsConstraint::setYOffset(float value) { + _yOffset = value; +} + +float PhysicsConstraint::getYOffset() { + return _yOffset; +} + +void PhysicsConstraint::setYVelocity(float value) { + _yVelocity = value; +} + +float PhysicsConstraint::getYVelocity() { + return _yVelocity; +} + +void PhysicsConstraint::setRotateOffset(float value) { + _rotateOffset = value; +} + +float PhysicsConstraint::getRotateOffset() { + return _rotateOffset; +} + +void PhysicsConstraint::setRotateVelocity(float value) { + _rotateVelocity = value; +} + +float PhysicsConstraint::getRotateVelocity() { + return _rotateVelocity; +} + +void PhysicsConstraint::setScaleOffset(float value) { + _scaleOffset = value; +} + +float PhysicsConstraint::getScaleOffset() { + return _scaleOffset; +} + +void PhysicsConstraint::setScaleVelocity(float value) { + _scaleVelocity = value; +} + +float PhysicsConstraint::getScaleVelocity() { + return _scaleVelocity; +} + +void PhysicsConstraint::setActive(bool value) { + _active = value; +} + +bool PhysicsConstraint::isActive() { + return _active; +} + +void PhysicsConstraint::setRemaining(float value) { + _remaining = value; +} + +float PhysicsConstraint::getRemaining() { + return _remaining; +} + +void PhysicsConstraint::setLastTime(float value) { + _lastTime = value; +} + +float PhysicsConstraint::getLastTime() { + return _lastTime; +} + +void PhysicsConstraint::reset() { + _remaining = 0; + _lastTime = _skeleton.getTime(); + _reset = true; + _xOffset = 0; + _xVelocity = 0; + _yOffset = 0; + _yVelocity = 0; + _rotateOffset = 0; + _rotateVelocity = 0; + _scaleOffset = 0; + _scaleVelocity = 0; +} + +void PhysicsConstraint::setToSetupPose() { + _inertia = _data.getInertia(); + _strength = _data.getStrength(); + _damping = _data.getDamping(); + _massInverse = _data.getMassInverse(); + _wind = _data.getWind(); + _gravity = _data.getGravity(); + _mix = _data.getMix(); +} +void PhysicsConstraint::update(Physics physics) { + float mix = _mix; + if (mix == 0) return; + + bool x = _data._x > 0; + bool y = _data._y > 0; + bool rotateOrShearX = _data._rotate > 0 || _data._shearX > 0; + bool scaleX = _data._scaleX > 0; + + Bone *bone = _bone; + float l = bone->_data.getLength(); + + switch (physics) { + case Physics::Physics_None: + return; + case Physics::Physics_Reset: + reset(); + // Fall through. + case Physics::Physics_Update: { + float delta = MathUtil::max(_skeleton.getTime() - _lastTime, 0.0f); + _remaining += delta; + _lastTime = _skeleton.getTime(); + + float bx = bone->_worldX, by = bone->_worldY; + if (_reset) { + _reset = false; + _ux = bx; + _uy = by; + } else { + float a = _remaining, i = _inertia, t = _data._step, f = _skeleton.getData()->getReferenceScale(); + float qx = _data._limit * delta, qy = qx * MathUtil::abs(_skeleton.getScaleX()); + qx *= MathUtil::abs(_skeleton.getScaleY()); + if (x || y) { + if (x) { + float u = (_ux - bx) * i; + _xOffset += u > qx ? qx : u < -qx ? -qx + : u; + _ux = bx; + } + if (y) { + float u = (_uy - by) * i; + _yOffset += u > qy ? qy : u < -qy ? -qy + : u; + _uy = by; + } + if (a >= t) { + float d = MathUtil::pow(_damping, 60 * t); + float m = _massInverse * t, e = _strength, w = _wind * f * _skeleton.getScaleX(), g = _gravity * f * _skeleton.getScaleY(); + do { + if (x) { + _xVelocity += (w - _xOffset * e) * m; + _xOffset += _xVelocity * t; + _xVelocity *= d; + } + if (y) { + _yVelocity -= (g + _yOffset * e) * m; + _yOffset += _yVelocity * t; + _yVelocity *= d; + } + a -= t; + } while (a >= t); + } + if (x) bone->_worldX += _xOffset * mix * _data._x; + if (y) bone->_worldY += _yOffset * mix * _data._y; + } + + if (rotateOrShearX || scaleX) { + float ca = MathUtil::atan2(bone->_c, bone->_a), c, s, mr = 0; + float dx = _cx - bone->_worldX, dy = _cy - bone->_worldY; + if (dx > qx) + dx = qx; + else if (dx < -qx)// + dx = -qx; + if (dy > qy) + dy = qy; + else if (dy < -qy)// + dy = -qy; + if (rotateOrShearX) { + mr = (_data._rotate + _data._shearX) * mix; + float r = MathUtil::atan2(dy + _ty, dx + _tx) - ca - _rotateOffset * mr; + _rotateOffset += (r - MathUtil::ceil(r * MathUtil::InvPi_2 - 0.5f) * MathUtil::Pi_2) * i; + r = _rotateOffset * mr + ca; + c = MathUtil::cos(r); + s = MathUtil::sin(r); + if (scaleX) { + r = l * bone->getWorldScaleX(); + if (r > 0) _scaleOffset += (dx * c + dy * s) * i / r; + } + } else { + c = MathUtil::cos(ca); + s = MathUtil::sin(ca); + float r = l * bone->getWorldScaleX(); + if (r > 0) _scaleOffset += (dx * c + dy * s) * i / r; + } + a = _remaining; + if (a >= t) { + float m = _massInverse * t, e = _strength, w = _wind, g = _gravity * (Bone::yDown ? -1 : 1), h = l / f; + float d = MathUtil::pow(_damping, 60 * t); + while (true) { + a -= t; + if (scaleX) { + _scaleVelocity += (w * c - g * s - _scaleOffset * e) * m; + _scaleOffset += _scaleVelocity * t; + _scaleVelocity *= d; + } + if (rotateOrShearX) { + _rotateVelocity -= ((w * s + g * c) * h + _rotateOffset * e) * m; + _rotateOffset += _rotateVelocity * t; + _rotateVelocity *= d; + if (a < t) break; + float r = _rotateOffset * mr + ca; + c = MathUtil::cos(r); + s = MathUtil::sin(r); + } else if (a < t)// + break; + } + } + } + _remaining = a; + } + + _cx = bone->_worldX; + _cy = bone->_worldY; + break; + } + case Physics::Physics_Pose: { + if (x) bone->_worldX += _xOffset * mix * _data._x; + if (y) bone->_worldY += _yOffset * mix * _data._y; + break; + } + } + + if (rotateOrShearX) { + float o = _rotateOffset * mix, s = 0, c = 0, a = 0; + if (_data._shearX > 0) { + float r = 0; + if (_data._rotate > 0) { + r = o * _data._rotate; + s = MathUtil::sin(r); + c = MathUtil::cos(r); + a = bone->_b; + bone->_b = c * a - s * bone->_d; + bone->_d = s * a + c * bone->_d; + } + r += o * _data._shearX; + s = MathUtil::sin(r); + c = MathUtil::cos(r); + a = bone->_a; + bone->_a = c * a - s * bone->_c; + bone->_c = s * a + c * bone->_c; + } else { + o *= _data._rotate; + s = MathUtil::sin(o); + c = MathUtil::cos(o); + a = bone->_a; + bone->_a = c * a - s * bone->_c; + bone->_c = s * a + c * bone->_c; + a = bone->_b; + bone->_b = c * a - s * bone->_d; + bone->_d = s * a + c * bone->_d; + } + } + if (scaleX) { + float s = 1 + _scaleOffset * mix * _data._scaleX; + bone->_a *= s; + bone->_c *= s; + } + if (physics != Physics::Physics_Pose) { + _tx = l * bone->_a; + _ty = l * bone->_c; + } + bone->updateAppliedTransform(); +} + +void PhysicsConstraint::rotate(float x, float y, float degrees) { + float r = degrees * MathUtil::Deg_Rad, cos = MathUtil::cos(r), sin = MathUtil::sin(r); + float dx = _cx - x, dy = _cy - y; + translate(dx * cos - dy * sin - dx, dx * sin + dy * cos - dy); +} + +void PhysicsConstraint::translate(float x, float y) { + _ux -= x; + _uy -= y; + _cx -= x; + _cy -= y; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PhysicsConstraintData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PhysicsConstraintData.cpp new file mode 100644 index 0000000..2c88a8d --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PhysicsConstraintData.cpp @@ -0,0 +1,223 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +#include + +using namespace spine; + +RTTI_IMPL(PhysicsConstraintData, ConstraintData) + +PhysicsConstraintData::PhysicsConstraintData(const String &name) : ConstraintData(name), + _bone(nullptr), + _x(0), _y(0), _rotate(0), _scaleX(0), _shearX(0), _limit(0), + _step(0), _inertia(0), _strength(0), _damping(0), _massInverse(0), _wind(0), _gravity(0), _mix(0), + _inertiaGlobal(false), _strengthGlobal(false), _dampingGlobal(false), _massGlobal(false), + _windGlobal(false), _gravityGlobal(false), _mixGlobal(false) { +} + + +void PhysicsConstraintData::setBone(BoneData *bone) { + _bone = bone; +} + +BoneData *PhysicsConstraintData::getBone() const { + return _bone; +} + +void PhysicsConstraintData::setX(float x) { + _x = x; +} + +float PhysicsConstraintData::getX() const { + return _x; +} + +void PhysicsConstraintData::setY(float y) { + _y = y; +} + +float PhysicsConstraintData::getY() const { + return _y; +} + +void PhysicsConstraintData::setRotate(float rotate) { + _rotate = rotate; +} + +float PhysicsConstraintData::getRotate() const { + return _rotate; +} + +void PhysicsConstraintData::setScaleX(float scaleX) { + _scaleX = scaleX; +} + +float PhysicsConstraintData::getScaleX() const { + return _scaleX; +} + +void PhysicsConstraintData::setShearX(float shearX) { + _shearX = shearX; +} + +float PhysicsConstraintData::getShearX() const { + return _shearX; +} + +void PhysicsConstraintData::setLimit(float limit) { + _limit = limit; +} + +float PhysicsConstraintData::getLimit() const { + return _limit; +} + +void PhysicsConstraintData::setStep(float step) { + _step = step; +} + +float PhysicsConstraintData::getStep() const { + return _step; +} + +void PhysicsConstraintData::setInertia(float inertia) { + _inertia = inertia; +} + +float PhysicsConstraintData::getInertia() const { + return _inertia; +} + +void PhysicsConstraintData::setStrength(float strength) { + _strength = strength; +} + +float PhysicsConstraintData::getStrength() const { + return _strength; +} + +void PhysicsConstraintData::setDamping(float damping) { + _damping = damping; +} + +float PhysicsConstraintData::getDamping() const { + return _damping; +} + +void PhysicsConstraintData::setMassInverse(float massInverse) { + _massInverse = massInverse; +} + +float PhysicsConstraintData::getMassInverse() const { + return _massInverse; +} + +void PhysicsConstraintData::setWind(float wind) { + _wind = wind; +} + +float PhysicsConstraintData::getWind() const { + return _wind; +} + +void PhysicsConstraintData::setGravity(float gravity) { + _gravity = gravity; +} + +float PhysicsConstraintData::getGravity() const { + return _gravity; +} + +void PhysicsConstraintData::setMix(float mix) { + _mix = mix; +} + +float PhysicsConstraintData::getMix() const { + return _mix; +} + +void PhysicsConstraintData::setInertiaGlobal(bool inertiaGlobal) { + _inertiaGlobal = inertiaGlobal; +} + +bool PhysicsConstraintData::isInertiaGlobal() const { + return _inertiaGlobal; +} + +void PhysicsConstraintData::setStrengthGlobal(bool strengthGlobal) { + _strengthGlobal = strengthGlobal; +} + +bool PhysicsConstraintData::isStrengthGlobal() const { + return _strengthGlobal; +} + +void PhysicsConstraintData::setDampingGlobal(bool dampingGlobal) { + _dampingGlobal = dampingGlobal; +} + +bool PhysicsConstraintData::isDampingGlobal() const { + return _dampingGlobal; +} + +void PhysicsConstraintData::setMassGlobal(bool massGlobal) { + _massGlobal = massGlobal; +} + +bool PhysicsConstraintData::isMassGlobal() const { + return _massGlobal; +} + +void PhysicsConstraintData::setWindGlobal(bool windGlobal) { + _windGlobal = windGlobal; +} + +bool PhysicsConstraintData::isWindGlobal() const { + return _windGlobal; +} + +void PhysicsConstraintData::setGravityGlobal(bool gravityGlobal) { + _gravityGlobal = gravityGlobal; +} + +bool PhysicsConstraintData::isGravityGlobal() const { + return _gravityGlobal; +} + +void PhysicsConstraintData::setMixGlobal(bool mixGlobal) { + _mixGlobal = mixGlobal; +} + +bool PhysicsConstraintData::isMixGlobal() const { + return _mixGlobal; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp new file mode 100644 index 0000000..4671e3f --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PhysicsConstraintTimeline.cpp @@ -0,0 +1,103 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(PhysicsConstraintTimeline, CurveTimeline) +RTTI_IMPL(PhysicsConstraintInertiaTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintStrengthTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintDampingTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintMassTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintWindTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintGravityTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintMixTimeline, PhysicsConstraintTimeline) +RTTI_IMPL(PhysicsConstraintResetTimeline, Timeline) + +PhysicsConstraintTimeline::PhysicsConstraintTimeline(size_t frameCount, size_t bezierCount, + int constraintIndex, Property property) : CurveTimeline1(frameCount, bezierCount), + _constraintIndex(constraintIndex) { + PropertyId ids[] = {((PropertyId) property << 32) | constraintIndex}; + setPropertyIds(ids, 1); +} + +void PhysicsConstraintTimeline::apply(Skeleton &skeleton, float, float time, Vector *, + float alpha, MixBlend blend, MixDirection) { + if (_constraintIndex == -1) { + float value = time >= _frames[0] ? getCurveValue(time) : 0; + + Vector &physicsConstraints = skeleton.getPhysicsConstraints(); + for (size_t i = 0; i < physicsConstraints.size(); i++) { + PhysicsConstraint *constraint = physicsConstraints[i]; + if (constraint->_active && global(constraint->_data)) + set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint), value)); + } + } else { + PhysicsConstraint *constraint = skeleton.getPhysicsConstraints()[_constraintIndex]; + if (constraint->_active) set(constraint, getAbsoluteValue(time, alpha, blend, get(constraint), setup(constraint))); + } +} + +void PhysicsConstraintResetTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *, float alpha, MixBlend blend, MixDirection direction) { + PhysicsConstraint *constraint = nullptr; + if (_constraintIndex != -1) { + constraint = skeleton.getPhysicsConstraints()[_constraintIndex]; + if (!constraint->_active) return; + } + + if (lastTime > time) {// Apply after lastTime for looped animations. + apply(skeleton, lastTime, FLT_MAX, nullptr, alpha, blend, direction); + lastTime = -1; + } else if (lastTime >= _frames[_frames.size() - 1])// Last time is after last frame. + return; + if (time < _frames[0]) return; + + if (lastTime < _frames[0] || time >= _frames[Animation::search(_frames, lastTime) + 1]) { + if (constraint != nullptr) + constraint->reset(); + else { + Vector &physicsConstraints = skeleton.getPhysicsConstraints(); + for (size_t i = 0; i < physicsConstraints.size(); i++) { + constraint = physicsConstraints[i]; + if (constraint->_active) constraint->reset(); + } + } + } +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PointAttachment.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PointAttachment.cpp new file mode 100644 index 0000000..611f3af --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/PointAttachment.cpp @@ -0,0 +1,88 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +#include + +using namespace spine; + +RTTI_IMPL(PointAttachment, Attachment) + +PointAttachment::PointAttachment(const String &name) : Attachment(name), _x(0), _y(0), _rotation(0), _color() { +} + +void PointAttachment::computeWorldPosition(Bone &bone, float &ox, float &oy) { + bone.localToWorld(_x, _y, ox, oy); +} + +float PointAttachment::computeWorldRotation(Bone &bone) { + float r = _rotation * MathUtil::Deg_Rad, cosine = MathUtil::cos(r), sine = MathUtil::sin(r); + float x = cosine * bone._a + sine * bone._b; + float y = cosine * bone._c + sine * bone._d; + return MathUtil::atan2Deg(y, x); +} + +float PointAttachment::getX() { + return _x; +} + +void PointAttachment::setX(float inValue) { + _x = inValue; +} + +float PointAttachment::getY() { + return _y; +} + +void PointAttachment::setY(float inValue) { + _y = inValue; +} + +float PointAttachment::getRotation() { + return _rotation; +} + +void PointAttachment::setRotation(float inValue) { + _rotation = inValue; +} + +Color &PointAttachment::getColor() { + return _color; +} + +Attachment *PointAttachment::copy() { + PointAttachment *copy = new (__FILE__, __LINE__) PointAttachment(getName()); + copy->_x = _x; + copy->_y = _y; + copy->_rotation = _rotation; + return copy; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/RTTI.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/RTTI.cpp new file mode 100644 index 0000000..04c3160 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/RTTI.cpp @@ -0,0 +1,56 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include + +using namespace spine; + +RTTI::RTTI(const char *className) : _className(className), _pBaseRTTI(NULL) { +} + +RTTI::RTTI(const char *className, const RTTI &baseRTTI) : _className(className), _pBaseRTTI(&baseRTTI) { +} + +const char *RTTI::getClassName() const { + return _className; +} + +bool RTTI::isExactly(const RTTI &rtti) const { + return !strcmp(this->_className, rtti._className); +} + +bool RTTI::instanceOf(const RTTI &rtti) const { + const RTTI *pCompare = this; + while (pCompare) { + if (!strcmp(pCompare->_className, rtti._className)) return true; + pCompare = pCompare->_pBaseRTTI; + } + return false; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/RegionAttachment.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/RegionAttachment.cpp new file mode 100644 index 0000000..65e1423 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/RegionAttachment.cpp @@ -0,0 +1,275 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(RegionAttachment, Attachment) + +const int RegionAttachment::BLX = 0; +const int RegionAttachment::BLY = 1; +const int RegionAttachment::ULX = 2; +const int RegionAttachment::ULY = 3; +const int RegionAttachment::URX = 4; +const int RegionAttachment::URY = 5; +const int RegionAttachment::BRX = 6; +const int RegionAttachment::BRY = 7; + +RegionAttachment::RegionAttachment(const String &name) : Attachment(name), + _x(0), + _y(0), + _rotation(0), + _scaleX(1), + _scaleY(1), + _width(0), + _height(0), + _path(), + _color(1, 1, 1, 1), + _region(NULL), + _sequence(NULL) { + _vertexOffset.setSize(NUM_UVS, 0); + _uvs.setSize(NUM_UVS, 0); +} + +RegionAttachment::~RegionAttachment() { + if (_sequence) delete _sequence; +} + +void RegionAttachment::updateRegion() { + if (_region == NULL) { + _uvs[BLX] = 0; + _uvs[BLY] = 0; + _uvs[ULX] = 0; + _uvs[ULY] = 1; + _uvs[URX] = 1; + _uvs[URY] = 1; + _uvs[BRX] = 1; + _uvs[BRY] = 0; + return; + } + + float regionScaleX = _width / _region->originalWidth * _scaleX; + float regionScaleY = _height / _region->originalHeight * _scaleY; + float localX = -_width / 2 * _scaleX + _region->offsetX * regionScaleX; + float localY = -_height / 2 * _scaleY + _region->offsetY * regionScaleY; + float localX2 = localX + _region->width * regionScaleX; + float localY2 = localY + _region->height * regionScaleY; + float cos = MathUtil::cosDeg(_rotation); + float sin = MathUtil::sinDeg(_rotation); + float localXCos = localX * cos + _x; + float localXSin = localX * sin; + float localYCos = localY * cos + _y; + float localYSin = localY * sin; + float localX2Cos = localX2 * cos + _x; + float localX2Sin = localX2 * sin; + float localY2Cos = localY2 * cos + _y; + float localY2Sin = localY2 * sin; + + _vertexOffset[BLX] = localXCos - localYSin; + _vertexOffset[BLY] = localYCos + localXSin; + _vertexOffset[ULX] = localXCos - localY2Sin; + _vertexOffset[ULY] = localY2Cos + localXSin; + _vertexOffset[URX] = localX2Cos - localY2Sin; + _vertexOffset[URY] = localY2Cos + localX2Sin; + _vertexOffset[BRX] = localX2Cos - localYSin; + _vertexOffset[BRY] = localYCos + localX2Sin; + + if (_region->degrees == 90) { + _uvs[URX] = _region->u; + _uvs[URY] = _region->v2; + _uvs[BRX] = _region->u; + _uvs[BRY] = _region->v; + _uvs[BLX] = _region->u2; + _uvs[BLY] = _region->v; + _uvs[ULX] = _region->u2; + _uvs[ULY] = _region->v2; + } else { + _uvs[ULX] = _region->u; + _uvs[ULY] = _region->v2; + _uvs[URX] = _region->u; + _uvs[URY] = _region->v; + _uvs[BRX] = _region->u2; + _uvs[BRY] = _region->v; + _uvs[BLX] = _region->u2; + _uvs[BLY] = _region->v2; + } +} + +void RegionAttachment::computeWorldVertices(Slot &slot, Vector &worldVertices, size_t offset, size_t stride) { + assert(worldVertices.size() >= (offset + 8)); + computeWorldVertices(slot, worldVertices.buffer(), offset, stride); +} + +void RegionAttachment::computeWorldVertices(Slot &slot, float *worldVertices, size_t offset, size_t stride) { + if (_sequence) _sequence->apply(&slot, this); + + Bone &bone = slot.getBone(); + float x = bone.getWorldX(), y = bone.getWorldY(); + float a = bone.getA(), b = bone.getB(), c = bone.getC(), d = bone.getD(); + float offsetX, offsetY; + + offsetX = _vertexOffset[BRX]; + offsetY = _vertexOffset[BRY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// br + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[BLX]; + offsetY = _vertexOffset[BLY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[ULX]; + offsetY = _vertexOffset[ULY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; + offset += stride; + + offsetX = _vertexOffset[URX]; + offsetY = _vertexOffset[URY]; + worldVertices[offset] = offsetX * a + offsetY * b + x;// ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + y; +} + +float RegionAttachment::getX() { + return _x; +} + +void RegionAttachment::setX(float inValue) { + _x = inValue; +} + +float RegionAttachment::getY() { + return _y; +} + +void RegionAttachment::setY(float inValue) { + _y = inValue; +} + +float RegionAttachment::getRotation() { + return _rotation; +} + +void RegionAttachment::setRotation(float inValue) { + _rotation = inValue; +} + +float RegionAttachment::getScaleX() { + return _scaleX; +} + +void RegionAttachment::setScaleX(float inValue) { + _scaleX = inValue; +} + +float RegionAttachment::getScaleY() { + return _scaleY; +} + +void RegionAttachment::setScaleY(float inValue) { + _scaleY = inValue; +} + +float RegionAttachment::getWidth() { + return _width; +} + +void RegionAttachment::setWidth(float inValue) { + _width = inValue; +} + +float RegionAttachment::getHeight() { + return _height; +} + +void RegionAttachment::setHeight(float inValue) { + _height = inValue; +} + +const String &RegionAttachment::getPath() { + return _path; +} + +void RegionAttachment::setPath(const String &inValue) { + _path = inValue; +} + +TextureRegion *RegionAttachment::getRegion() { + return _region; +} + +void RegionAttachment::setRegion(TextureRegion *region) { + _region = region; +} + +Sequence *RegionAttachment::getSequence() { + return _sequence; +} + +void RegionAttachment::setSequence(Sequence *sequence) { + _sequence = sequence; +} + +Vector &RegionAttachment::getOffset() { + return _vertexOffset; +} + +Vector &RegionAttachment::getUVs() { + return _uvs; +} + +spine::Color &RegionAttachment::getColor() { + return _color; +} + +Attachment *RegionAttachment::copy() { + RegionAttachment *copy = new (__FILE__, __LINE__) RegionAttachment(getName()); + copy->_region = _region; + copy->_path = _path; + copy->_x = _x; + copy->_y = _y; + copy->_scaleX = _scaleX; + copy->_scaleY = _scaleY; + copy->_rotation = _rotation; + copy->_width = _width; + copy->_height = _height; + copy->_uvs.clearAndAddAll(_uvs); + copy->_vertexOffset.clearAndAddAll(_vertexOffset); + copy->_color.set(_color); + copy->_sequence = _sequence != NULL ? _sequence->copy() : NULL; + return copy; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/RotateTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/RotateTimeline.cpp new file mode 100644 index 0000000..4c53f30 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/RotateTimeline.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(RotateTimeline, CurveTimeline1) + +RotateTimeline::RotateTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_Rotate << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +void RotateTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->isActive()) bone->_rotation = getRelativeValue(time, alpha, blend, bone->_rotation, bone->getData()._rotation); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ScaleTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ScaleTimeline.cpp new file mode 100644 index 0000000..f2fe50c --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ScaleTimeline.cpp @@ -0,0 +1,194 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(ScaleTimeline, CurveTimeline2) + +ScaleTimeline::ScaleTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline2(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ScaleX << 32) | boneIndex, + ((PropertyId) Property_ScaleY << 32) | boneIndex}; + setPropertyIds(ids, 2); +} + +ScaleTimeline::~ScaleTimeline() {} + +void ScaleTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *bone = skeleton._bones[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_scaleX = bone->_data._scaleX; + bone->_scaleY = bone->_data._scaleY; + return; + case MixBlend_First: + bone->_scaleX += (bone->_data._scaleX - bone->_scaleX) * alpha; + bone->_scaleY += (bone->_data._scaleY - bone->_scaleY) * alpha; + default: { + } + } + return; + } + + float x, y; + int i = Animation::search(_frames, time, CurveTimeline2::ENTRIES); + int curveType = (int) _curves[i / CurveTimeline2::ENTRIES]; + switch (curveType) { + case CurveTimeline::LINEAR: { + float before = _frames[i]; + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + float t = (time - before) / (_frames[i + CurveTimeline2::ENTRIES] - before); + x += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE1] - x) * t; + y += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE2] - y) * t; + break; + } + case CurveTimeline::STEPPED: { + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + break; + } + default: { + x = getBezierValue(time, i, CurveTimeline2::VALUE1, curveType - CurveTimeline2::BEZIER); + y = getBezierValue(time, i, CurveTimeline2::VALUE2, + curveType + CurveTimeline2::BEZIER_SIZE - CurveTimeline2::BEZIER); + } + } + x *= bone->_data._scaleX; + y *= bone->_data._scaleY; + + if (alpha == 1) { + if (blend == MixBlend_Add) { + bone->_scaleX += x - bone->_data._scaleX; + bone->_scaleY += y - bone->_data._scaleY; + } else { + bone->_scaleX = x; + bone->_scaleY = y; + } + } else { + float bx, by; + if (direction == MixDirection_Out) { + switch (blend) { + case MixBlend_Setup: + bx = bone->_data._scaleX; + by = bone->_data._scaleY; + bone->_scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bx) * alpha; + bone->_scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - by) * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bx = bone->_scaleX; + by = bone->_scaleY; + bone->_scaleX = bx + (MathUtil::abs(x) * MathUtil::sign(bx) - bx) * alpha; + bone->_scaleY = by + (MathUtil::abs(y) * MathUtil::sign(by) - by) * alpha; + break; + case MixBlend_Add: + bone->_scaleX += (x - bone->_data._scaleX) * alpha; + bone->_scaleY += (y - bone->_data._scaleY) * alpha; + } + } else { + switch (blend) { + case MixBlend_Setup: + bx = MathUtil::abs(bone->_data._scaleX) * MathUtil::sign(x); + by = MathUtil::abs(bone->_data._scaleY) * MathUtil::sign(y); + bone->_scaleX = bx + (x - bx) * alpha; + bone->_scaleY = by + (y - by) * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bx = MathUtil::abs(bone->_scaleX) * MathUtil::sign(x); + by = MathUtil::abs(bone->_scaleY) * MathUtil::sign(y); + bone->_scaleX = bx + (x - bx) * alpha; + bone->_scaleY = by + (y - by) * alpha; + break; + case MixBlend_Add: + bone->_scaleX += (x - bone->_data._scaleX) * alpha; + bone->_scaleY += (y - bone->_data._scaleY) * alpha; + } + } + } +} + +RTTI_IMPL(ScaleXTimeline, CurveTimeline1) + +ScaleXTimeline::ScaleXTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ScaleX << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ScaleXTimeline::~ScaleXTimeline() {} + +void ScaleXTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_scaleX = getScaleValue(time, alpha, blend, direction, bone->_scaleX, bone->_data._scaleX); +} + +RTTI_IMPL(ScaleYTimeline, CurveTimeline1) + +ScaleYTimeline::ScaleYTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ScaleY << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ScaleYTimeline::~ScaleYTimeline() {} + +void ScaleYTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_scaleY = getScaleValue(time, alpha, blend, direction, bone->_scaleX, bone->_data._scaleY); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Sequence.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Sequence.cpp new file mode 100644 index 0000000..f690dce --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Sequence.cpp @@ -0,0 +1,96 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include +#include +#include + +using namespace spine; + +Sequence::Sequence(int count) : _id(Sequence::getNextID()), + _regions(), + _start(0), + _digits(0), + _setupIndex(0) { + _regions.setSize(count, NULL); +} + +Sequence::~Sequence() { +} + +Sequence *Sequence::copy() { + Sequence *copy = new (__FILE__, __LINE__) Sequence((int) _regions.size()); + for (size_t i = 0; i < _regions.size(); i++) { + copy->_regions[i] = _regions[i]; + } + copy->_start = _start; + copy->_digits = _digits; + copy->_setupIndex = _setupIndex; + return copy; +} + +void Sequence::apply(Slot *slot, Attachment *attachment) { + int index = slot->getSequenceIndex(); + if (index == -1) index = _setupIndex; + if (index >= (int) _regions.size()) index = (int) _regions.size() - 1; + TextureRegion *region = _regions[index]; + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = static_cast(attachment); + if (regionAttachment->getRegion() != region) { + regionAttachment->setRegion(region); + regionAttachment->updateRegion(); + } + } + + if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + MeshAttachment *meshAttachment = static_cast(attachment); + if (meshAttachment->getRegion() != region) { + meshAttachment->setRegion(region); + meshAttachment->updateRegion(); + } + } +} + +String Sequence::getPath(const String &basePath, int index) { + String result(basePath); + String frame; + frame.append(_start + index); + for (int i = _digits - (int) frame.length(); i > 0; i--) + result.append("0"); + result.append(frame); + return result; +} + +int Sequence::getNextID() { + static int _nextID = 0; + return _nextID; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SequenceTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SequenceTimeline.cpp new file mode 100644 index 0000000..f00bd82 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SequenceTimeline.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(SequenceTimeline, Timeline) + +SequenceTimeline::SequenceTimeline(size_t frameCount, int slotIndex, Attachment *attachment) : Timeline(frameCount, ENTRIES), _slotIndex(slotIndex), _attachment(attachment) { + int sequenceId = 0; + if (attachment->getRTTI().instanceOf(RegionAttachment::rtti)) sequenceId = ((RegionAttachment *) attachment)->getSequence()->getId(); + if (attachment->getRTTI().instanceOf(MeshAttachment::rtti)) sequenceId = ((MeshAttachment *) attachment)->getSequence()->getId(); + PropertyId ids[] = {((PropertyId) Property_Sequence << 32) | ((slotIndex << 16 | sequenceId) & 0xffffffff)}; + setPropertyIds(ids, 1); +} + +SequenceTimeline::~SequenceTimeline() { +} + +void SequenceTimeline::setFrame(int frame, float time, SequenceMode mode, int index, float delay) { + Vector &frames = this->_frames; + frame *= ENTRIES; + frames[frame] = time; + frames[frame + MODE] = mode | (index << 4); + frames[frame + DELAY] = delay; +} + +void SequenceTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(alpha); + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Slot *slot = skeleton.getSlots()[_slotIndex]; + if (!slot->getBone().isActive()) return; + Attachment *slotAttachment = slot->getAttachment(); + if (slotAttachment != _attachment) { + if (slotAttachment == NULL || !slotAttachment->getRTTI().instanceOf(VertexAttachment::rtti) || ((VertexAttachment *) slotAttachment)->getTimelineAttachment() != _attachment) return; + } + Sequence *sequence = NULL; + if (_attachment->getRTTI().instanceOf(RegionAttachment::rtti)) sequence = ((RegionAttachment *) _attachment)->getSequence(); + if (_attachment->getRTTI().instanceOf(MeshAttachment::rtti)) sequence = ((MeshAttachment *) _attachment)->getSequence(); + if (!sequence) return; + + if (direction == MixDirection_Out) { + if (blend == MixBlend_Setup) slot->setSequenceIndex(-1); + return; + } + + Vector &frames = this->_frames; + if (time < frames[0]) {// Time is before first frame. + if (blend == MixBlend_Setup || blend == MixBlend_First) slot->setSequenceIndex(-1); + return; + } + + int i = Animation::search(frames, time, ENTRIES); + float before = frames[i]; + int modeAndIndex = (int) frames[i + MODE]; + float delay = frames[i + DELAY]; + + int index = modeAndIndex >> 4, count = (int) sequence->getRegions().size(); + int mode = modeAndIndex & 0xf; + if (mode != SequenceMode::hold) { + index += (int) (((time - before) / delay + 0.0001)); + switch (mode) { + case SequenceMode::once: + index = MathUtil::min(count - 1, index); + break; + case SequenceMode::loop: + index %= count; + break; + case SequenceMode::pingpong: { + int n = (count << 1) - 2; + index = n == 0 ? 0 : index % n; + if (index >= count) index = n - index; + break; + } + case SequenceMode::onceReverse: + index = MathUtil::max(count - 1 - index, 0); + break; + case SequenceMode::loopReverse: + index = count - 1 - (index % count); + break; + case SequenceMode::pingpongReverse: { + int n = (count << 1) - 2; + index = n == 0 ? 0 : (index + count - 1) % n; + if (index >= count) index = n - index; + } + } + } + slot->setSequenceIndex(index); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ShearTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ShearTimeline.cpp new file mode 100644 index 0000000..027f70c --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/ShearTimeline.cpp @@ -0,0 +1,162 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(ShearTimeline, CurveTimeline2) + +ShearTimeline::ShearTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline2(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ShearX << 32) | boneIndex, + ((PropertyId) Property_ShearY << 32) | boneIndex}; + setPropertyIds(ids, 2); +} + +ShearTimeline::~ShearTimeline() { +} + +void ShearTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_shearX = bone->_data._shearX; + bone->_shearY = bone->_data._shearY; + return; + case MixBlend_First: + bone->_shearX += (bone->_data._shearX - bone->_shearX) * alpha; + bone->_shearY += (bone->_data._shearY - bone->_shearY) * alpha; + default: { + } + } + return; + } + + float x, y; + int i = Animation::search(_frames, time, CurveTimeline2::ENTRIES); + int curveType = (int) _curves[i / CurveTimeline2::ENTRIES]; + switch (curveType) { + case CurveTimeline2::LINEAR: { + float before = _frames[i]; + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + float t = (time - before) / (_frames[i + CurveTimeline2::ENTRIES] - before); + x += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE1] - x) * t; + y += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE2] - y) * t; + break; + } + case CurveTimeline2::STEPPED: { + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + break; + } + default: { + x = getBezierValue(time, i, CurveTimeline2::VALUE1, curveType - CurveTimeline2::BEZIER); + y = getBezierValue(time, i, CurveTimeline2::VALUE2, + curveType + CurveTimeline2::BEZIER_SIZE - CurveTimeline2::BEZIER); + } + } + + switch (blend) { + case MixBlend_Setup: + bone->_shearX = bone->_data._shearX + x * alpha; + bone->_shearY = bone->_data._shearY + y * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bone->_shearX += (bone->_data._shearX + x - bone->_shearX) * alpha; + bone->_shearY += (bone->_data._shearY + y - bone->_shearY) * alpha; + break; + case MixBlend_Add: + bone->_shearX += x * alpha; + bone->_shearY += y * alpha; + } +} + +RTTI_IMPL(ShearXTimeline, CurveTimeline1) + +ShearXTimeline::ShearXTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ShearX << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ShearXTimeline::~ShearXTimeline() { +} + +void ShearXTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_shearX = getRelativeValue(time, alpha, blend, bone->_shearX, bone->_data._shearX); +} + +RTTI_IMPL(ShearYTimeline, CurveTimeline1) + +ShearYTimeline::ShearYTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_ShearX << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +ShearYTimeline::~ShearYTimeline() { +} + +void ShearYTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_shearY = getRelativeValue(time, alpha, blend, bone->_shearY, bone->_data._shearY); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Skeleton.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Skeleton.cpp new file mode 100644 index 0000000..84292a9 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Skeleton.cpp @@ -0,0 +1,774 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace spine; + +Skeleton::Skeleton(SkeletonData *skeletonData) + : _data(skeletonData), _skin(NULL), _color(1, 1, 1, 1), _scaleX(1), + _scaleY(1), _x(0), _y(0), _time(0) { + _bones.ensureCapacity(_data->getBones().size()); + for (size_t i = 0; i < _data->getBones().size(); ++i) { + BoneData *data = _data->getBones()[i]; + + Bone *bone; + if (data->getParent() == NULL) { + bone = new (__FILE__, __LINE__) Bone(*data, *this, NULL); + } else { + Bone *parent = _bones[data->getParent()->getIndex()]; + bone = new (__FILE__, __LINE__) Bone(*data, *this, parent); + parent->getChildren().add(bone); + } + + _bones.add(bone); + } + + _slots.ensureCapacity(_data->getSlots().size()); + _drawOrder.ensureCapacity(_data->getSlots().size()); + for (size_t i = 0; i < _data->getSlots().size(); ++i) { + SlotData *data = _data->getSlots()[i]; + + Bone *bone = _bones[data->getBoneData().getIndex()]; + Slot *slot = new (__FILE__, __LINE__) Slot(*data, *bone); + + _slots.add(slot); + _drawOrder.add(slot); + } + + _ikConstraints.ensureCapacity(_data->getIkConstraints().size()); + for (size_t i = 0; i < _data->getIkConstraints().size(); ++i) { + IkConstraintData *data = _data->getIkConstraints()[i]; + + IkConstraint *constraint = + new (__FILE__, __LINE__) IkConstraint(*data, *this); + + _ikConstraints.add(constraint); + } + + _transformConstraints.ensureCapacity(_data->getTransformConstraints().size()); + for (size_t i = 0; i < _data->getTransformConstraints().size(); ++i) { + TransformConstraintData *data = _data->getTransformConstraints()[i]; + + TransformConstraint *constraint = + new (__FILE__, __LINE__) TransformConstraint(*data, *this); + + _transformConstraints.add(constraint); + } + + _pathConstraints.ensureCapacity(_data->getPathConstraints().size()); + for (size_t i = 0; i < _data->getPathConstraints().size(); ++i) { + PathConstraintData *data = _data->getPathConstraints()[i]; + + PathConstraint *constraint = + new (__FILE__, __LINE__) PathConstraint(*data, *this); + + _pathConstraints.add(constraint); + } + + _physicsConstraints.ensureCapacity(_data->getPhysicsConstraints().size()); + for (size_t i = 0; i < _data->getPhysicsConstraints().size(); ++i) { + PhysicsConstraintData *data = _data->getPhysicsConstraints()[i]; + + PhysicsConstraint *constraint = + new (__FILE__, __LINE__) PhysicsConstraint(*data, *this); + + _physicsConstraints.add(constraint); + } + + updateCache(); +} + +Skeleton::~Skeleton() { + ContainerUtil::cleanUpVectorOfPointers(_bones); + ContainerUtil::cleanUpVectorOfPointers(_slots); + ContainerUtil::cleanUpVectorOfPointers(_ikConstraints); + ContainerUtil::cleanUpVectorOfPointers(_transformConstraints); + ContainerUtil::cleanUpVectorOfPointers(_pathConstraints); + ContainerUtil::cleanUpVectorOfPointers(_physicsConstraints); +} + +void Skeleton::updateCache() { + _updateCache.clear(); + + for (size_t i = 0, n = _bones.size(); i < n; ++i) { + Bone *bone = _bones[i]; + bone->_sorted = bone->_data.isSkinRequired(); + bone->_active = !bone->_sorted; + } + + if (_skin) { + Vector &skinBones = _skin->getBones(); + for (size_t i = 0, n = skinBones.size(); i < n; i++) { + Bone *bone = _bones[skinBones[i]->getIndex()]; + do { + bone->_sorted = false; + bone->_active = true; + bone = bone->_parent; + } while (bone); + } + } + + size_t ikCount = _ikConstraints.size(); + size_t transformCount = _transformConstraints.size(); + size_t pathCount = _pathConstraints.size(); + size_t physicsCount = _physicsConstraints.size(); + size_t constraintCount = ikCount + transformCount + pathCount + physicsCount; + + size_t i = 0; +continue_outer: + for (; i < constraintCount; ++i) { + for (size_t ii = 0; ii < ikCount; ++ii) { + IkConstraint *constraint = _ikConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortIkConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < transformCount; ++ii) { + TransformConstraint *constraint = _transformConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortTransformConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < pathCount; ++ii) { + PathConstraint *constraint = _pathConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortPathConstraint(constraint); + i++; + goto continue_outer; + } + } + + for (size_t ii = 0; ii < physicsCount; ++ii) { + PhysicsConstraint *constraint = _physicsConstraints[ii]; + if (constraint->getData().getOrder() == i) { + sortPhysicsConstraint(constraint); + i++; + goto continue_outer; + } + } + } + + size_t n = _bones.size(); + for (i = 0; i < n; ++i) { + sortBone(_bones[i]); + } +} + +void Skeleton::printUpdateCache() { + for (size_t i = 0; i < _updateCache.size(); i++) { + Updatable *updatable = _updateCache[i]; + if (updatable->getRTTI().isExactly(Bone::rtti)) { + printf("bone %s\n", ((Bone *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(TransformConstraint::rtti)) { + printf("transform constraint %s\n", + ((TransformConstraint *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(IkConstraint::rtti)) { + printf("ik constraint %s\n", + ((IkConstraint *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(PathConstraint::rtti)) { + printf("path constraint %s\n", + ((PathConstraint *) updatable)->getData().getName().buffer()); + } else if (updatable->getRTTI().isExactly(PhysicsConstraint::rtti)) { + printf("physics constraint %s\n", + ((PhysicsConstraint *) updatable)->getData().getName().buffer()); + } + } +} + +void Skeleton::updateWorldTransform(Physics physics) { + for (size_t i = 0, n = _bones.size(); i < n; i++) { + Bone *bone = _bones[i]; + bone->_ax = bone->_x; + bone->_ay = bone->_y; + bone->_arotation = bone->_rotation; + bone->_ascaleX = bone->_scaleX; + bone->_ascaleY = bone->_scaleY; + bone->_ashearX = bone->_shearX; + bone->_ashearY = bone->_shearY; + } + + for (size_t i = 0, n = _updateCache.size(); i < n; ++i) { + Updatable *updatable = _updateCache[i]; + updatable->update(physics); + } +} + +void Skeleton::updateWorldTransform(Physics physics, Bone *parent) { + // Apply the parent bone transform to the root bone. The root bone always + // inherits scale, rotation and reflection. + Bone *rootBone = getRootBone(); + float pa = parent->_a, pb = parent->_b, pc = parent->_c, pd = parent->_d; + rootBone->_worldX = pa * _x + pb * _y + parent->_worldX; + rootBone->_worldY = pc * _x + pd * _y + parent->_worldY; + + float rx = (rootBone->_rotation + rootBone->_shearX) * MathUtil::Deg_Rad; + float ry = (rootBone->_rotation + 90 + rootBone->_shearY) * MathUtil::Deg_Rad; + float la = MathUtil::cos(rx) * rootBone->_scaleX; + float lb = MathUtil::cos(ry) * rootBone->_scaleY; + float lc = MathUtil::sin(rx) * rootBone->_scaleX; + float ld = MathUtil::sin(ry) * rootBone->_scaleY; + rootBone->_a = (pa * la + pb * lc) * _scaleX; + rootBone->_b = (pa * lb + pb * ld) * _scaleX; + rootBone->_c = (pc * la + pd * lc) * _scaleY; + rootBone->_d = (pc * lb + pd * ld) * _scaleY; + + // Update everything except root bone. + Bone *rb = getRootBone(); + for (size_t i = 0, n = _updateCache.size(); i < n; i++) { + Updatable *updatable = _updateCache[i]; + if (updatable != rb) + updatable->update(physics); + } +} + +void Skeleton::setToSetupPose() { + setBonesToSetupPose(); + setSlotsToSetupPose(); +} + +void Skeleton::setBonesToSetupPose() { + for (size_t i = 0, n = _bones.size(); i < n; ++i) { + _bones[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) { + _ikConstraints[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) { + _transformConstraints[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) { + _pathConstraints[i]->setToSetupPose(); + } + + for (size_t i = 0, n = _physicsConstraints.size(); i < n; ++i) { + _physicsConstraints[i]->setToSetupPose(); + } +} + +void Skeleton::setSlotsToSetupPose() { + _drawOrder.clear(); + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + _drawOrder.add(_slots[i]); + } + + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + _slots[i]->setToSetupPose(); + } +} + +Bone *Skeleton::findBone(const String &boneName) { + return ContainerUtil::findWithDataName(_bones, boneName); +} + +Slot *Skeleton::findSlot(const String &slotName) { + return ContainerUtil::findWithDataName(_slots, slotName); +} + +void Skeleton::setSkin(const String &skinName) { + Skin *foundSkin = skinName.isEmpty() ? NULL : _data->findSkin(skinName); + setSkin(foundSkin); +} + +void Skeleton::setSkin(Skin *newSkin) { + if (_skin == newSkin) + return; + if (newSkin != NULL) { + if (_skin != NULL) { + Skeleton &thisRef = *this; + newSkin->attachAll(thisRef, *_skin); + } else { + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + Slot *slotP = _slots[i]; + Slot &slot = *slotP; + const String &name = slot._data.getAttachmentName(); + if (name.length() > 0) { + Attachment *attachment = newSkin->getAttachment(i, name); + if (attachment != NULL) { + slot.setAttachment(attachment); + } + } + } + } + } + + _skin = newSkin; + updateCache(); +} + +Attachment *Skeleton::getAttachment(const String &slotName, + const String &attachmentName) { + return getAttachment(_data->findSlot(slotName)->getIndex(), attachmentName); +} + +Attachment *Skeleton::getAttachment(int slotIndex, + const String &attachmentName) { + if (attachmentName.isEmpty()) + return NULL; + + if (_skin != NULL) { + Attachment *attachment = _skin->getAttachment(slotIndex, attachmentName); + if (attachment != NULL) { + return attachment; + } + } + + return _data->getDefaultSkin() != NULL + ? _data->getDefaultSkin()->getAttachment(slotIndex, attachmentName) + : NULL; +} + +void Skeleton::setAttachment(const String &slotName, + const String &attachmentName) { + assert(slotName.length() > 0); + + for (size_t i = 0, n = _slots.size(); i < n; ++i) { + Slot *slot = _slots[i]; + if (slot->_data.getName() == slotName) { + Attachment *attachment = NULL; + if (attachmentName.length() > 0) { + attachment = getAttachment((int) i, attachmentName); + + assert(attachment != NULL); + } + + slot->setAttachment(attachment); + + return; + } + } + + printf("Slot not found: %s", slotName.buffer()); + + assert(false); +} + +IkConstraint *Skeleton::findIkConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _ikConstraints.size(); i < n; ++i) { + IkConstraint *ikConstraint = _ikConstraints[i]; + if (ikConstraint->_data.getName() == constraintName) { + return ikConstraint; + } + } + return NULL; +} + +TransformConstraint * +Skeleton::findTransformConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _transformConstraints.size(); i < n; ++i) { + TransformConstraint *transformConstraint = _transformConstraints[i]; + if (transformConstraint->_data.getName() == constraintName) { + return transformConstraint; + } + } + + return NULL; +} + +PathConstraint *Skeleton::findPathConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _pathConstraints.size(); i < n; ++i) { + PathConstraint *constraint = _pathConstraints[i]; + if (constraint->_data.getName() == constraintName) { + return constraint; + } + } + + return NULL; +} + +PhysicsConstraint * +Skeleton::findPhysicsConstraint(const String &constraintName) { + assert(constraintName.length() > 0); + + for (size_t i = 0, n = _physicsConstraints.size(); i < n; ++i) { + PhysicsConstraint *constraint = _physicsConstraints[i]; + if (constraint->_data.getName() == constraintName) { + return constraint; + } + } + + return NULL; +} + +void Skeleton::getBounds(float &outX, float &outY, float &outWidth, + float &outHeight, Vector &outVertexBuffer) { + getBounds(outX, outY, outWidth, outHeight, outVertexBuffer, NULL); +} + +void Skeleton::getBounds(float &outX, float &outY, float &outWidth, + float &outHeight, Vector &outVertexBuffer, SkeletonClipping *clipper) { + static unsigned short quadIndices[] = {0, 1, 2, 2, 3, 0}; + float minX = FLT_MAX; + float minY = FLT_MAX; + float maxX = -FLT_MAX; + float maxY = -FLT_MAX; + + for (size_t i = 0; i < _drawOrder.size(); ++i) { + Slot *slot = _drawOrder[i]; + if (!slot->_bone._active) + continue; + size_t verticesLength = 0; + Attachment *attachment = slot->getAttachment(); + unsigned short *triangles = NULL; + size_t trianglesLength = 0; + + if (attachment != NULL && + attachment->getRTTI().instanceOf(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = + static_cast(attachment); + + verticesLength = 8; + if (outVertexBuffer.size() < 8) { + outVertexBuffer.setSize(8, 0); + } + regionAttachment->computeWorldVertices(*slot, outVertexBuffer, 0); + triangles = quadIndices; + trianglesLength = 6; + } else if (attachment != NULL && + attachment->getRTTI().instanceOf(MeshAttachment::rtti)) { + MeshAttachment *mesh = static_cast(attachment); + + verticesLength = mesh->getWorldVerticesLength(); + if (outVertexBuffer.size() < verticesLength) { + outVertexBuffer.setSize(verticesLength, 0); + } + + mesh->computeWorldVertices(*slot, 0, verticesLength, + outVertexBuffer.buffer(), 0); + triangles = mesh->getTriangles().buffer(); + trianglesLength = mesh->getTriangles().size(); + } else if (attachment != NULL && + attachment->getRTTI().instanceOf(ClippingAttachment::rtti) && clipper != NULL) { + clipper->clipStart(*slot, static_cast(attachment)); + continue; + } + + if (verticesLength > 0) { + float *vertices = outVertexBuffer.buffer(); + if (clipper != NULL && clipper->isClipping()) { + clipper->clipTriangles(outVertexBuffer.buffer(), triangles, trianglesLength); + vertices = clipper->getClippedVertices().buffer(); + verticesLength = clipper->getClippedVertices().size(); + } + for (size_t ii = 0; ii < verticesLength; ii += 2) { + float vx = vertices[ii]; + float vy = vertices[ii + 1]; + + minX = MathUtil::min(minX, vx); + minY = MathUtil::min(minY, vy); + maxX = MathUtil::max(maxX, vx); + maxY = MathUtil::max(maxY, vy); + } + } + if (clipper != NULL) clipper->clipEnd(*slot); + } + if (clipper != NULL) clipper->clipEnd(); + + outX = minX; + outY = minY; + outWidth = maxX - minX; + outHeight = maxY - minY; +} + +Bone *Skeleton::getRootBone() { return _bones.size() == 0 ? NULL : _bones[0]; } + +SkeletonData *Skeleton::getData() { return _data; } + +Vector &Skeleton::getBones() { return _bones; } + +Vector &Skeleton::getUpdateCacheList() { return _updateCache; } + +Vector &Skeleton::getSlots() { return _slots; } + +Vector &Skeleton::getDrawOrder() { return _drawOrder; } + +Vector &Skeleton::getIkConstraints() { return _ikConstraints; } + +Vector &Skeleton::getPathConstraints() { + return _pathConstraints; +} + +Vector &Skeleton::getTransformConstraints() { + return _transformConstraints; +} + +Vector &Skeleton::getPhysicsConstraints() { + return _physicsConstraints; +} + +Skin *Skeleton::getSkin() { return _skin; } + +Color &Skeleton::getColor() { return _color; } + +void Skeleton::setPosition(float x, float y) { + _x = x; + _y = y; +} + +float Skeleton::getX() { return _x; } + +void Skeleton::setX(float inValue) { _x = inValue; } + +float Skeleton::getY() { return _y; } + +void Skeleton::setY(float inValue) { _y = inValue; } + +float Skeleton::getScaleX() { return _scaleX; } + +void Skeleton::setScaleX(float inValue) { _scaleX = inValue; } + +float Skeleton::getScaleY() { return _scaleY * (Bone::isYDown() ? -1 : 1); } + +void Skeleton::setScaleY(float inValue) { _scaleY = inValue; } + +void Skeleton::sortIkConstraint(IkConstraint *constraint) { + constraint->_active = + constraint->_target->_active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + Bone *target = constraint->getTarget(); + sortBone(target); + + Vector &constrained = constraint->getBones(); + Bone *parent = constrained[0]; + sortBone(parent); + + if (constrained.size() == 1) { + _updateCache.add(constraint); + sortReset(parent->_children); + } else { + Bone *child = constrained[constrained.size() - 1]; + sortBone(child); + + _updateCache.add(constraint); + + sortReset(parent->_children); + child->_sorted = true; + } +} + +void Skeleton::sortPathConstraint(PathConstraint *constraint) { + constraint->_active = + constraint->_target->_bone._active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + Slot *slot = constraint->getTarget(); + int slotIndex = slot->getData().getIndex(); + Bone &slotBone = slot->getBone(); + if (_skin != NULL) + sortPathConstraintAttachment(_skin, slotIndex, slotBone); + if (_data->_defaultSkin != NULL && _data->_defaultSkin != _skin) + sortPathConstraintAttachment(_data->_defaultSkin, slotIndex, slotBone); + for (size_t ii = 0, nn = _data->_skins.size(); ii < nn; ii++) + sortPathConstraintAttachment(_data->_skins[ii], slotIndex, slotBone); + + Attachment *attachment = slot->getAttachment(); + if (attachment != NULL && + attachment->getRTTI().instanceOf(PathAttachment::rtti)) + sortPathConstraintAttachment(attachment, slotBone); + + Vector &constrained = constraint->getBones(); + size_t boneCount = constrained.size(); + for (size_t i = 0; i < boneCount; ++i) { + sortBone(constrained[i]); + } + + _updateCache.add(constraint); + + for (size_t i = 0; i < boneCount; i++) + sortReset(constrained[i]->getChildren()); + for (size_t i = 0; i < boneCount; i++) + constrained[i]->_sorted = true; +} + +void Skeleton::sortTransformConstraint(TransformConstraint *constraint) { + constraint->_active = + constraint->_target->_active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + sortBone(constraint->getTarget()); + + Vector &constrained = constraint->getBones(); + size_t boneCount = constrained.size(); + if (constraint->_data.isLocal()) { + for (size_t i = 0; i < boneCount; i++) { + Bone *child = constrained[i]; + sortBone(child->getParent()); + sortBone(child); + } + } else { + for (size_t i = 0; i < boneCount; ++i) { + sortBone(constrained[i]); + } + } + + _updateCache.add(constraint); + + for (size_t i = 0; i < boneCount; ++i) + sortReset(constrained[i]->getChildren()); + for (size_t i = 0; i < boneCount; ++i) + constrained[i]->_sorted = true; +} + +void Skeleton::sortPhysicsConstraint(PhysicsConstraint *constraint) { + Bone *bone = constraint->getBone(); + constraint->_active = + bone->_active && + (!constraint->_data.isSkinRequired() || + (_skin && _skin->_constraints.contains(&constraint->_data))); + if (!constraint->_active) + return; + + sortBone(bone); + _updateCache.add(constraint); + sortReset(bone->getChildren()); + bone->_sorted = true; +} + +void Skeleton::sortPathConstraintAttachment(Skin *skin, size_t slotIndex, + Bone &slotBone) { + Skin::AttachmentMap::Entries attachments = skin->getAttachments(); + + while (attachments.hasNext()) { + Skin::AttachmentMap::Entry entry = attachments.next(); + if (entry._slotIndex == slotIndex) { + Attachment *value = entry._attachment; + sortPathConstraintAttachment(value, slotBone); + } + } +} + +void Skeleton::sortPathConstraintAttachment(Attachment *attachment, + Bone &slotBone) { + if (attachment == NULL || + !attachment->getRTTI().instanceOf(PathAttachment::rtti)) + return; + Vector &pathBones = + static_cast(attachment)->getBones(); + if (pathBones.size() == 0) + sortBone(&slotBone); + else { + for (size_t i = 0, n = pathBones.size(); i < n;) { + size_t nn = pathBones[i++]; + nn += i; + while (i < nn) { + sortBone(_bones[pathBones[i++]]); + } + } + } +} + +void Skeleton::sortBone(Bone *bone) { + if (bone->_sorted) + return; + Bone *parent = bone->_parent; + if (parent != NULL) + sortBone(parent); + bone->_sorted = true; + _updateCache.add(bone); +} + +void Skeleton::sortReset(Vector &bones) { + for (size_t i = 0, n = bones.size(); i < n; ++i) { + Bone *bone = bones[i]; + if (!bone->_active) + continue; + if (bone->_sorted) + sortReset(bone->getChildren()); + bone->_sorted = false; + } +} + +float Skeleton::getTime() { return _time; } + +void Skeleton::setTime(float time) { _time = time; } + +void Skeleton::update(float delta) { _time += delta; } + +void Skeleton::physicsTranslate(float x, float y) { + for (int i = 0; i < (int) _physicsConstraints.size(); i++) { + _physicsConstraints[i]->translate(x, y); + } +} + +void Skeleton::physicsRotate(float x, float y, float degrees) { + for (int i = 0; i < (int) _physicsConstraints.size(); i++) { + _physicsConstraints[i]->rotate(x, y, degrees); + } +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonBinary.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonBinary.cpp new file mode 100644 index 0000000..a82ada1 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonBinary.cpp @@ -0,0 +1,1490 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +SkeletonBinary::SkeletonBinary(Atlas *atlasArray) : _attachmentLoader( + new (__FILE__, __LINE__) AtlasAttachmentLoader(atlasArray)), + _error(), _scale(1), _ownsLoader(true) { +} + +SkeletonBinary::SkeletonBinary(AttachmentLoader *attachmentLoader, bool ownsLoader) : _attachmentLoader( + attachmentLoader), + _error(), + _scale(1), + _ownsLoader(ownsLoader) { + assert(_attachmentLoader != NULL); +} + +SkeletonBinary::~SkeletonBinary() { + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + if (_ownsLoader) delete _attachmentLoader; +} + +SkeletonData *SkeletonBinary::readSkeletonData(const unsigned char *binary, const int length) { + bool nonessential; + SkeletonData *skeletonData; + + DataInput *input = new (__FILE__, __LINE__) DataInput(); + input->cursor = binary; + input->end = binary + length; + + _linkedMeshes.clear(); + + skeletonData = new (__FILE__, __LINE__) SkeletonData(); + + char buffer[16] = {0}; + int lowHash = readInt(input); + int hightHash = readInt(input); + String hashString; + snprintf(buffer, 16, "%x", hightHash); + hashString.append(buffer); + snprintf(buffer, 16, "%x", lowHash); + hashString.append(buffer); + skeletonData->_hash = hashString; + + char *skeletonDataVersion = readString(input); + skeletonData->_version.own(skeletonDataVersion); + + if (!skeletonData->_version.startsWith(SPINE_VERSION_STRING)) { + char errorMsg[255]; + snprintf(errorMsg, 255, "Skeleton version %s does not match runtime version %s", skeletonData->_version.buffer(), SPINE_VERSION_STRING); + setError(errorMsg, ""); + delete input; + delete skeletonData; + return NULL; + } + + skeletonData->_x = readFloat(input); + skeletonData->_y = readFloat(input); + skeletonData->_width = readFloat(input); + skeletonData->_height = readFloat(input); + skeletonData->_referenceScale = readFloat(input) * this->_scale; + + nonessential = readBoolean(input); + + if (nonessential) { + skeletonData->_fps = readFloat(input); + skeletonData->_imagesPath.own(readString(input)); + skeletonData->_audioPath.own(readString(input)); + } + + int numStrings = readVarint(input, true); + for (int i = 0; i < numStrings; i++) + skeletonData->_strings.add(readString(input)); + + /* Bones. */ + int numBones = readVarint(input, true); + skeletonData->_bones.setSize(numBones, 0); + for (int i = 0; i < numBones; ++i) { + const char *name = readString(input); + BoneData *parent = i == 0 ? 0 : skeletonData->_bones[readVarint(input, true)]; + BoneData *data = new (__FILE__, __LINE__) BoneData(i, String(name, true), parent); + data->_rotation = readFloat(input); + data->_x = readFloat(input) * _scale; + data->_y = readFloat(input) * _scale; + data->_scaleX = readFloat(input); + data->_scaleY = readFloat(input); + data->_shearX = readFloat(input); + data->_shearY = readFloat(input); + data->_length = readFloat(input) * _scale; + data->_inherit = static_cast(readVarint(input, true)); + data->_skinRequired = readBoolean(input); + if (nonessential) { + readColor(input, data->getColor()); + data->_icon.own(readString(input)); + data->_visible = readBoolean(input); + } + skeletonData->_bones[i] = data; + } + + /* Slots. */ + int slotsCount = readVarint(input, true); + skeletonData->_slots.setSize(slotsCount, 0); + for (int i = 0; i < slotsCount; ++i) { + String slotName = String(readString(input), true); + BoneData *boneData = skeletonData->_bones[readVarint(input, true)]; + SlotData *slotData = new (__FILE__, __LINE__) SlotData(i, slotName, *boneData); + + readColor(input, slotData->getColor()); + unsigned char a = readByte(input); + unsigned char r = readByte(input); + unsigned char g = readByte(input); + unsigned char b = readByte(input); + if (!(r == 0xff && g == 0xff && b == 0xff && a == 0xff)) { + slotData->getDarkColor().set(r / 255.0f, g / 255.0f, b / 255.0f, 1); + slotData->setHasDarkColor(true); + } + slotData->_attachmentName = readStringRef(input, skeletonData); + slotData->_blendMode = static_cast(readVarint(input, true)); + if (nonessential) { + slotData->_visible = readBoolean(input); + } + skeletonData->_slots[i] = slotData; + } + + /* IK constraints. */ + int ikConstraintsCount = readVarint(input, true); + skeletonData->_ikConstraints.setSize(ikConstraintsCount, 0); + for (int i = 0; i < ikConstraintsCount; ++i) { + const char *name = readString(input); + IkConstraintData *data = new (__FILE__, __LINE__) IkConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_bones[readVarint(input, true)]; + int flags = readByte(input); + data->_skinRequired = (flags & 1) != 0; + data->_bendDirection = (flags & 2) != 0 ? 1 : -1; + data->_compress = (flags & 4) != 0; + data->_stretch = (flags & 8) != 0; + data->_uniform = (flags & 16) != 0; + if ((flags & 32) != 0) data->_mix = (flags & 64) != 0 ? readFloat(input) : 1; + if ((flags & 128) != 0) data->_softness = readFloat(input) * _scale; + + skeletonData->_ikConstraints[i] = data; + } + + /* Transform constraints. */ + int transformConstraintsCount = readVarint(input, true); + skeletonData->_transformConstraints.setSize(transformConstraintsCount, 0); + for (int i = 0; i < transformConstraintsCount; ++i) { + const char *name = readString(input); + TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_bones[readVarint(input, true)]; + int flags = readByte(input); + data->_skinRequired = (flags & 1) != 0; + data->_local = (flags & 2) != 0; + data->_relative = (flags & 4) != 0; + if ((flags & 8) != 0) data->_offsetRotation = readFloat(input); + if ((flags & 16) != 0) data->_offsetX = readFloat(input) * _scale; + if ((flags & 32) != 0) data->_offsetY = readFloat(input) * _scale; + if ((flags & 64) != 0) data->_offsetScaleX = readFloat(input); + if ((flags & 128) != 0) data->_offsetScaleY = readFloat(input); + flags = readByte(input); + if ((flags & 1) != 0) data->_offsetShearY = readFloat(input); + if ((flags & 2) != 0) data->_mixRotate = readFloat(input); + if ((flags & 4) != 0) data->_mixX = readFloat(input); + if ((flags & 8) != 0) data->_mixY = readFloat(input); + if ((flags & 16) != 0) data->_mixScaleX = readFloat(input); + if ((flags & 32) != 0) data->_mixScaleY = readFloat(input); + if ((flags & 64) != 0) data->_mixShearY = readFloat(input); + + skeletonData->_transformConstraints[i] = data; + } + + /* Path constraints */ + int pathConstraintsCount = readVarint(input, true); + skeletonData->_pathConstraints.setSize(pathConstraintsCount, 0); + for (int i = 0; i < pathConstraintsCount; ++i) { + const char *name = readString(input); + PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData(String(name, true)); + data->setOrder(readVarint(input, true)); + data->setSkinRequired(readBoolean(input)); + int bonesCount = readVarint(input, true); + data->_bones.setSize(bonesCount, 0); + for (int ii = 0; ii < bonesCount; ++ii) + data->_bones[ii] = skeletonData->_bones[readVarint(input, true)]; + data->_target = skeletonData->_slots[readVarint(input, true)]; + int flags = readByte(input); + data->_positionMode = (PositionMode) (flags & 1); + data->_spacingMode = (SpacingMode) ((flags >> 1) & 3); + data->_rotateMode = (RotateMode) ((flags >> 3) & 3); + if ((flags & 128) != 0) data->_offsetRotation = readFloat(input); + data->_position = readFloat(input); + if (data->_positionMode == PositionMode_Fixed) data->_position *= _scale; + data->_spacing = readFloat(input); + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) + data->_spacing *= _scale; + data->_mixRotate = readFloat(input); + data->_mixX = readFloat(input); + data->_mixY = readFloat(input); + skeletonData->_pathConstraints[i] = data; + } + + // Physics constraints. + int physicsConstraintsCount = readVarint(input, true); + skeletonData->_physicsConstraints.setSize(physicsConstraintsCount, 0); + for (int i = 0; i < physicsConstraintsCount; i++) { + const char *name = readString(input); + PhysicsConstraintData *data = new (__FILE__, __LINE__) PhysicsConstraintData(String(name, true)); + data->_order = readVarint(input, true); + data->_bone = skeletonData->_bones[readVarint(input, true)]; + int flags = readByte(input); + data->_skinRequired = (flags & 1) != 0; + if ((flags & 2) != 0) data->_x = readFloat(input); + if ((flags & 4) != 0) data->_y = readFloat(input); + if ((flags & 8) != 0) data->_rotate = readFloat(input); + if ((flags & 16) != 0) data->_scaleX = readFloat(input); + if ((flags & 32) != 0) data->_shearX = readFloat(input); + data->_limit = ((flags & 64) != 0 ? readFloat(input) : 5000) * _scale; + data->_step = 1.f / readByte(input); + data->_inertia = readFloat(input); + data->_strength = readFloat(input); + data->_damping = readFloat(input); + data->_massInverse = (flags & 128) != 0 ? readFloat(input) : 1; + data->_wind = readFloat(input); + data->_gravity = readFloat(input); + flags = readByte(input); + if ((flags & 1) != 0) data->_inertiaGlobal = true; + if ((flags & 2) != 0) data->_strengthGlobal = true; + if ((flags & 4) != 0) data->_dampingGlobal = true; + if ((flags & 8) != 0) data->_massGlobal = true; + if ((flags & 16) != 0) data->_windGlobal = true; + if ((flags & 32) != 0) data->_gravityGlobal = true; + if ((flags & 64) != 0) data->_mixGlobal = true; + data->_mix = (flags & 128) != 0 ? readFloat(input) : 1; + skeletonData->_physicsConstraints[i] = data; + } + + /* Default skin. */ + Skin *defaultSkin = readSkin(input, true, skeletonData, nonessential); + if (defaultSkin) { + skeletonData->_defaultSkin = defaultSkin; + skeletonData->_skins.add(defaultSkin); + } + + if (!this->getError().isEmpty()) { + delete input; + delete skeletonData; + return NULL; + } + + /* Skins. */ + for (size_t i = 0, n = (size_t) readVarint(input, true); i < n; ++i) { + Skin *skin = readSkin(input, false, skeletonData, nonessential); + if (skin) + skeletonData->_skins.add(skin); + else { + delete input; + delete skeletonData; + return NULL; + } + } + + /* Linked meshes. */ + for (int i = 0, n = (int) _linkedMeshes.size(); i < n; ++i) { + LinkedMesh *linkedMesh = _linkedMeshes[i]; + Skin *skin = skeletonData->_skins[linkedMesh->_skinIndex]; + Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent); + if (parent == NULL) { + delete input; + delete skeletonData; + setError("Parent mesh not found: ", linkedMesh->_parent.buffer()); + return NULL; + } + linkedMesh->_mesh->_timelineAttachment = linkedMesh->_inheritTimeline ? static_cast(parent) + : linkedMesh->_mesh; + linkedMesh->_mesh->setParentMesh(static_cast(parent)); + if (linkedMesh->_mesh->_region) linkedMesh->_mesh->updateRegion(); + _attachmentLoader->configureAttachment(linkedMesh->_mesh); + } + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + /* Events. */ + int eventsCount = readVarint(input, true); + skeletonData->_events.setSize(eventsCount, 0); + for (int i = 0; i < eventsCount; ++i) { + const char *name = readString(input); + EventData *eventData = new (__FILE__, __LINE__) EventData(String(name, true)); + eventData->_intValue = readVarint(input, false); + eventData->_floatValue = readFloat(input); + eventData->_stringValue.own(readString(input)); + eventData->_audioPath.own(readString(input)); + if (!eventData->_audioPath.isEmpty()) { + eventData->_volume = readFloat(input); + eventData->_balance = readFloat(input); + } + skeletonData->_events[i] = eventData; + } + + /* Animations. */ + int animationsCount = readVarint(input, true); + skeletonData->_animations.setSize(animationsCount, 0); + for (int i = 0; i < animationsCount; ++i) { + String name(readString(input), true); + Animation *animation = readAnimation(name, input, skeletonData); + if (!animation) { + delete input; + delete skeletonData; + return NULL; + } + skeletonData->_animations[i] = animation; + } + + delete input; + return skeletonData; +} + +SkeletonData *SkeletonBinary::readSkeletonDataFile(const String &path) { + int length; + SkeletonData *skeletonData; + const char *binary = SpineExtension::readFile(path.buffer(), &length); + if (length == 0 || !binary) { + setError("Unable to read skeleton file: ", path.buffer()); + return NULL; + } + skeletonData = readSkeletonData((unsigned char *) binary, length); + SpineExtension::free(binary, __FILE__, __LINE__); + return skeletonData; +} + +void SkeletonBinary::setError(const char *value1, const char *value2) { + char message[256]; + int length; + strcpy(message, value1); + length = (int) strlen(value1); + if (value2) strncat(message + length, value2, 255 - length); + _error = String(message); +} + +char *SkeletonBinary::readString(DataInput *input) { + int length = readVarint(input, true); + char *string; + if (length == 0) return NULL; + string = SpineExtension::alloc(length, __FILE__, __LINE__); + memcpy(string, input->cursor, length - 1); + input->cursor += length - 1; + string[length - 1] = '\0'; + return string; +} + +char *SkeletonBinary::readStringRef(DataInput *input, SkeletonData *skeletonData) { + int index = readVarint(input, true); + return index == 0 ? NULL : skeletonData->_strings[index - 1]; +} + +float SkeletonBinary::readFloat(DataInput *input) { + union { + int intValue; + float floatValue; + } intToFloat; + intToFloat.intValue = readInt(input); + return intToFloat.floatValue; +} + +unsigned char SkeletonBinary::readByte(DataInput *input) { + return *input->cursor++; +} + +signed char SkeletonBinary::readSByte(DataInput *input) { + return (signed char) readByte(input); +} + +bool SkeletonBinary::readBoolean(DataInput *input) { + return readByte(input) != 0; +} + +int SkeletonBinary::readInt(DataInput *input) { + int result = readByte(input); + result <<= 8; + result |= readByte(input); + result <<= 8; + result |= readByte(input); + result <<= 8; + result |= readByte(input); + return result; +} + +void SkeletonBinary::readColor(DataInput *input, Color &color) { + color.r = readByte(input) / 255.0f; + color.g = readByte(input) / 255.0f; + color.b = readByte(input) / 255.0f; + color.a = readByte(input) / 255.0f; +} + +int SkeletonBinary::readVarint(DataInput *input, bool optimizePositive) { + unsigned char b = readByte(input); + int value = b & 0x7F; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 7; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 14; + if (b & 0x80) { + b = readByte(input); + value |= (b & 0x7F) << 21; + if (b & 0x80) value |= (readByte(input) & 0x7F) << 28; + } + } + } + if (!optimizePositive) value = (((unsigned int) value >> 1) ^ -(value & 1)); + return value; +} + +Skin *SkeletonBinary::readSkin(DataInput *input, bool defaultSkin, SkeletonData *skeletonData, bool nonessential) { + Skin *skin; + int slotCount = 0; + if (defaultSkin) { + slotCount = readVarint(input, true); + if (slotCount == 0) return NULL; + skin = new (__FILE__, __LINE__) Skin("default"); + } else { + skin = new (__FILE__, __LINE__) Skin(String(readString(input), true)); + + if (nonessential) readColor(input, skin->getColor()); + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int boneIndex = readVarint(input, true); + if (boneIndex >= (int) skeletonData->_bones.size()) return NULL; + skin->getBones().add(skeletonData->_bones[boneIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int ikIndex = readVarint(input, true); + if (ikIndex >= (int) skeletonData->_ikConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_ikConstraints[ikIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int transformIndex = readVarint(input, true); + if (transformIndex >= (int) skeletonData->_transformConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_transformConstraints[transformIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int pathIndex = readVarint(input, true); + if (pathIndex >= (int) skeletonData->_pathConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_pathConstraints[pathIndex]); + } + + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int physicsIndex = readVarint(input, true); + if (physicsIndex >= (int) skeletonData->_physicsConstraints.size()) return NULL; + skin->getConstraints().add(skeletonData->_physicsConstraints[physicsIndex]); + } + slotCount = readVarint(input, true); + } + + for (int i = 0; i < slotCount; ++i) { + int slotIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + String name(readStringRef(input, skeletonData)); + Attachment *attachment = readAttachment(input, skin, slotIndex, name, skeletonData, nonessential); + if (attachment) + skin->setAttachment(slotIndex, String(name), attachment); + else { + delete skin; + return NULL; + } + } + } + return skin; +} + +Sequence *SkeletonBinary::readSequence(DataInput *input) { + Sequence *sequence = new (__FILE__, __LINE__) Sequence(readVarint(input, true)); + sequence->_start = readVarint(input, true); + sequence->_digits = readVarint(input, true); + sequence->_setupIndex = readVarint(input, true); + return sequence; +} + +Attachment *SkeletonBinary::readAttachment(DataInput *input, Skin *skin, int slotIndex, const String &attachmentName, + SkeletonData *skeletonData, bool nonessential) { + + int flags = readByte(input); + String name = (flags & 8) != 0 ? readStringRef(input, skeletonData) : attachmentName; + AttachmentType type = static_cast(flags & 0x7); + switch (type) { + case AttachmentType_Region: { + String path = (flags & 16) != 0 ? readStringRef(input, skeletonData) : name; + Color color(1, 1, 1, 1); + if ((flags & 32) != 0) readColor(input, color); + Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; + float rotation = (flags & 128) != 0 ? readFloat(input) : 0; + float x = readFloat(input) * _scale; + float y = readFloat(input) * _scale; + float scaleX = readFloat(input); + float scaleY = readFloat(input); + float width = readFloat(input) * _scale; + float height = readFloat(input) * _scale; + RegionAttachment *region = _attachmentLoader->newRegionAttachment(*skin, String(name), String(path), sequence); + if (!region) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + region->_path = path; + region->_rotation = rotation; + region->_x = x; + region->_y = y; + region->_scaleX = scaleX; + region->_scaleY = scaleY; + region->_width = width; + region->_height = height; + region->getColor().set(color); + region->_sequence = sequence; + if (sequence == NULL) region->updateRegion(); + _attachmentLoader->configureAttachment(region); + return region; + } + case AttachmentType_Boundingbox: { + BoundingBoxAttachment *box = _attachmentLoader->newBoundingBoxAttachment(*skin, String(name)); + if (!box) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + int verticesLength = readVertices(input, box->getVertices(), box->getBones(), (flags & 16) != 0); + box->setWorldVerticesLength(verticesLength); + if (nonessential) { + readColor(input, box->getColor()); + } + _attachmentLoader->configureAttachment(box); + return box; + } + case AttachmentType_Mesh: { + Vector uvs; + Vector triangles; + Vector vertices; + Vector bones; + int hullLength; + float width = 0; + float height = 0; + Vector edges; + + String path = (flags & 16) != 0 ? readStringRef(input, skeletonData) : name; + Color color(1, 1, 1, 1); + if ((flags & 32) != 0) readColor(input, color); + Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; + hullLength = readVarint(input, true); + int verticesLength = readVertices(input, vertices, bones, (flags & 128) != 0); + readFloatArray(input, verticesLength, 1, uvs); + readShortArray(input, triangles, (verticesLength - hullLength - 2) * 3); + + if (nonessential) { + readShortArray(input, edges, readVarint(input, true)); + width = readFloat(input); + height = readFloat(input); + } + + MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path), sequence); + if (!mesh) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + mesh->_path = path; + mesh->_color.set(color); + mesh->_bones.addAll(bones); + mesh->_vertices.addAll(vertices); + mesh->setWorldVerticesLength(verticesLength); + mesh->_triangles.addAll(triangles); + mesh->_regionUVs.addAll(uvs); + if (sequence == NULL) mesh->updateRegion(); + mesh->_hullLength = hullLength; + mesh->_sequence = sequence; + if (nonessential) { + mesh->_edges.addAll(edges); + mesh->_width = width; + mesh->_height = height; + } + _attachmentLoader->configureAttachment(mesh); + return mesh; + } + case AttachmentType_Linkedmesh: { + String path = (flags & 16) != 0 ? readStringRef(input, skeletonData) : name; + Color color(1, 1, 1, 1); + if ((flags & 32) != 0) readColor(input, color); + Sequence *sequence = (flags & 64) != 0 ? readSequence(input) : nullptr; + bool inheritTimelines = (flags & 128) != 0; + int skinIndex = readVarint(input, true); + String parent(readStringRef(input, skeletonData)); + float width = 0, height = 0; + if (nonessential) { + width = readFloat(input) * _scale; + height = readFloat(input) * _scale; + } + + MeshAttachment *mesh = _attachmentLoader->newMeshAttachment(*skin, String(name), String(path), sequence); + if (!mesh) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + mesh->_path = path; + mesh->_color.set(color); + mesh->_sequence = sequence; + if (nonessential) { + mesh->_width = width; + mesh->_height = height; + } + + LinkedMesh *linkedMesh = new (__FILE__, __LINE__) LinkedMesh(mesh, skinIndex, slotIndex, + String(parent), inheritTimelines); + _linkedMeshes.add(linkedMesh); + return mesh; + } + case AttachmentType_Path: { + PathAttachment *path = _attachmentLoader->newPathAttachment(*skin, String(name)); + if (!path) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + path->_closed = (flags & 16) != 0; + path->_constantSpeed = (flags & 32) != 0; + int verticesLength = readVertices(input, path->getVertices(), path->getBones(), (flags & 64) != 0); + path->setWorldVerticesLength(verticesLength); + int lengthsLength = verticesLength / 6; + path->_lengths.setSize(lengthsLength, 0); + for (int i = 0; i < lengthsLength; ++i) { + path->_lengths[i] = readFloat(input) * _scale; + } + if (nonessential) { + readColor(input, path->getColor()); + } + _attachmentLoader->configureAttachment(path); + return path; + } + case AttachmentType_Point: { + PointAttachment *point = _attachmentLoader->newPointAttachment(*skin, String(name)); + if (!point) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + point->_rotation = readFloat(input); + point->_x = readFloat(input) * _scale; + point->_y = readFloat(input) * _scale; + + if (nonessential) { + readColor(input, point->getColor()); + } + _attachmentLoader->configureAttachment(point); + return point; + } + case AttachmentType_Clipping: { + int endSlotIndex = readVarint(input, true); + ClippingAttachment *clip = _attachmentLoader->newClippingAttachment(*skin, name); + if (!clip) { + setError("Error reading attachment: ", name.buffer()); + return NULL; + } + int verticesLength = readVertices(input, clip->getVertices(), clip->getBones(), (flags & 16) != 0); + clip->setWorldVerticesLength(verticesLength); + clip->_endSlot = skeletonData->_slots[endSlotIndex]; + if (nonessential) { + readColor(input, clip->getColor()); + } + _attachmentLoader->configureAttachment(clip); + return clip; + } + } + return NULL; +} + +int SkeletonBinary::readVertices(DataInput *input, Vector &vertices, Vector &bones, bool weighted) { + float scale = _scale; + int vertexCount = readVarint(input, true); + int verticesLength = vertexCount << 1; + if (!weighted) { + readFloatArray(input, verticesLength, scale, vertices); + return verticesLength; + } + vertices.ensureCapacity(verticesLength * 3 * 3); + bones.ensureCapacity(verticesLength * 3); + for (int i = 0; i < vertexCount; ++i) { + int boneCount = readVarint(input, true); + bones.add(boneCount); + for (int ii = 0; ii < boneCount; ++ii) { + bones.add(readVarint(input, true)); + vertices.add(readFloat(input) * scale); + vertices.add(readFloat(input) * scale); + vertices.add(readFloat(input)); + } + } + return verticesLength; +} + +void SkeletonBinary::readFloatArray(DataInput *input, int n, float scale, Vector &array) { + array.setSize(n, 0); + + int i; + if (scale == 1) { + for (i = 0; i < n; ++i) { + array[i] = readFloat(input); + } + } else { + for (i = 0; i < n; ++i) { + array[i] = readFloat(input) * scale; + } + } +} + +void SkeletonBinary::readShortArray(DataInput *input, Vector &array, int n) { + array.setSize(n, 0); + for (int i = 0; i < n; ++i) { + array[i] = (short) readVarint(input, true); + } +} + +void SkeletonBinary::setBezier(DataInput *input, CurveTimeline *timeline, int bezier, int frame, int value, float time1, + float time2, + float value1, float value2, float scale) { + float cx1 = readFloat(input); + float cy1 = readFloat(input); + float cx2 = readFloat(input); + float cy2 = readFloat(input); + timeline->setBezier(bezier, frame, value, time1, value1, cx1, cy1 * scale, cx2, cy2 * scale, time2, value2); +} + +void SkeletonBinary::readTimeline(DataInput *input, Vector &timelines, CurveTimeline1 *timeline, float scale) { + float time = readFloat(input); + float value = readFloat(input) * scale; + for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { + timeline->setFrame(frame, time, value); + if (frame == frameLast) break; + float time2 = readFloat(input); + float value2 = readFloat(input) * scale; + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); + } + time = time2; + value = value2; + } + timelines.add(timeline); +} + +void SkeletonBinary::readTimeline2(DataInput *input, Vector &timelines, CurveTimeline2 *timeline, float scale) { + float time = readFloat(input); + float value1 = readFloat(input) * scale; + float value2 = readFloat(input) * scale; + for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { + timeline->setFrame(frame, time, value1, value2); + if (frame == frameLast) break; + float time2 = readFloat(input); + float nvalue1 = readFloat(input) * scale; + float nvalue2 = readFloat(input) * scale; + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale); + setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + } + timelines.add(timeline); +} + +Animation *SkeletonBinary::readAnimation(const String &name, DataInput *input, SkeletonData *skeletonData) { + Vector timelines; + float scale = _scale; + int numTimelines = readVarint(input, true); + SP_UNUSED(numTimelines); + // Slot timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int slotIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + unsigned char timelineType = readByte(input); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + switch (timelineType) { + case SLOT_ATTACHMENT: { + AttachmentTimeline *timeline = new (__FILE__, __LINE__) AttachmentTimeline(frameCount, slotIndex); + for (int frame = 0; frame < frameCount; ++frame) { + float time = readFloat(input); + String attachmentName(readStringRef(input, skeletonData)); + timeline->setFrame(frame, time, attachmentName); + } + timelines.add(timeline); + break; + } + case SLOT_RGBA: { + int bezierCount = readVarint(input, true); + RGBATimeline *timeline = new (__FILE__, __LINE__) RGBATimeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + float a = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b, a); + if (frame == frameLast) break; + + float time2 = readFloat(input); + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + float a2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2, 1); + } + time = time2; + r = r2; + g = g2; + b = b2; + a = a2; + } + timelines.add(timeline); + break; + } + case SLOT_RGB: { + int bezierCount = readVarint(input, true); + RGBTimeline *timeline = new (__FILE__, __LINE__) RGBTimeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b); + if (frame == frameLast) break; + + float time2 = readFloat(input); + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1); + } + time = time2; + r = r2; + g = g2; + b = b2; + } + timelines.add(timeline); + break; + } + case SLOT_RGBA2: { + int bezierCount = readVarint(input, true); + RGBA2Timeline *timeline = new (__FILE__, __LINE__) RGBA2Timeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + float a = readByte(input) / 255.0; + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b, a, r2, g2, b2); + if (frame == frameLast) break; + float time2 = readFloat(input); + float nr = readByte(input) / 255.0; + float ng = readByte(input) / 255.0; + float nb = readByte(input) / 255.0; + float na = readByte(input) / 255.0; + float nr2 = readByte(input) / 255.0; + float ng2 = readByte(input) / 255.0; + float nb2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, a, na, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2, 1); + setBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.add(timeline); + break; + } + case SLOT_RGB2: { + int bezierCount = readVarint(input, true); + RGB2Timeline *timeline = new (__FILE__, __LINE__) RGB2Timeline(frameCount, bezierCount, slotIndex); + + float time = readFloat(input); + float r = readByte(input) / 255.0; + float g = readByte(input) / 255.0; + float b = readByte(input) / 255.0; + float r2 = readByte(input) / 255.0; + float g2 = readByte(input) / 255.0; + float b2 = readByte(input) / 255.0; + + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, r, g, b, r2, g2, b2); + if (frame == frameLast) break; + float time2 = readFloat(input); + float nr = readByte(input) / 255.0; + float ng = readByte(input) / 255.0; + float nb = readByte(input) / 255.0; + float nr2 = readByte(input) / 255.0; + float ng2 = readByte(input) / 255.0; + float nb2 = readByte(input) / 255.0; + + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, r2, nr2, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, g2, ng2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.add(timeline); + break; + } + case SLOT_ALPHA: { + int bezierCount = readVarint(input, true); + AlphaTimeline *timeline = new (__FILE__, __LINE__) AlphaTimeline(frameCount, bezierCount, slotIndex); + float time = readFloat(input); + float a = readByte(input) / 255.0; + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, a); + if (frame == frameLast) break; + float time2 = readFloat(input); + float a2 = readByte(input) / 255.0; + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); + } + time = time2; + a = a2; + } + timelines.add(timeline); + break; + } + default: { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Invalid timeline type for a slot: ", skeletonData->_slots[slotIndex]->_name.buffer()); + return NULL; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int boneIndex = readVarint(input, true); + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + unsigned char timelineType = readByte(input); + int frameCount = readVarint(input, true); + if (timelineType == BONE_INHERIT) { + InheritTimeline *timeline = new (__FILE__, __LINE__) InheritTimeline(frameCount, boneIndex); + for (int frame = 0; frame < frameCount; frame++) { + float time = readFloat(input); + Inherit inherit = (Inherit) readByte(input); + timeline->setFrame(frame, time, inherit); + } + timelines.add(timeline); + continue; + } + int bezierCount = readVarint(input, true); + switch (timelineType) { + case BONE_ROTATE: + readTimeline(input, timelines, + new (__FILE__, __LINE__) RotateTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_TRANSLATE: + readTimeline2(input, timelines, new (__FILE__, __LINE__) TranslateTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEX: + readTimeline(input, timelines, new (__FILE__, __LINE__) TranslateXTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEY: + readTimeline(input, timelines, new (__FILE__, __LINE__) TranslateYTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_SCALE: + readTimeline2(input, timelines, + new (__FILE__, __LINE__) ScaleTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SCALEX: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ScaleXTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SCALEY: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ScaleYTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SHEAR: + readTimeline2(input, timelines, + new (__FILE__, __LINE__) ShearTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SHEARX: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ShearXTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + case BONE_SHEARY: + readTimeline(input, timelines, + new (__FILE__, __LINE__) ShearYTimeline(frameCount, bezierCount, boneIndex), + 1); + break; + default: { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Invalid timeline type for a bone: ", skeletonData->_bones[boneIndex]->_name.buffer()); + return NULL; + } + } + } + } + + // IK timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + int bezierCount = readVarint(input, true); + IkConstraintTimeline *timeline = new (__FILE__, __LINE__) IkConstraintTimeline(frameCount, bezierCount, index); + int flags = readByte(input); + float time = readFloat(input), mix = (flags & 1) != 0 ? ((flags & 2) != 0 ? readFloat(input) : 1) : 0; + float softness = (flags & 4) != 0 ? readFloat(input) * scale : 0; + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mix, softness, (flags & 8) != 0 ? 1 : -1, (flags & 16) != 0, (flags & 32) != 0); + if (frame == frameLast) break; + flags = readByte(input); + float time2 = readFloat(input), mix2 = (flags & 1) != 0 ? ((flags & 2) != 0 ? readFloat(input) : 1) : 0; + float softness2 = (flags & 4) != 0 ? readFloat(input) * scale : 0; + if ((flags & 64) != 0) + timeline->setStepped(frame); + else if ((flags & 128) != 0) { + setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); + } + time = time2; + mix = mix2; + softness = softness2; + } + timelines.add(timeline); + } + + // Transform constraint timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + int bezierCount = readVarint(input, true); + TransformConstraintTimeline *timeline = new TransformConstraintTimeline(frameCount, bezierCount, index); + float time = readFloat(input); + float mixRotate = readFloat(input); + float mixX = readFloat(input); + float mixY = readFloat(input); + float mixScaleX = readFloat(input); + float mixScaleY = readFloat(input); + float mixShearY = readFloat(input); + for (int frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (frame == frameLast) break; + float time2 = readFloat(input); + float mixRotate2 = readFloat(input); + float mixX2 = readFloat(input); + float mixY2 = readFloat(input); + float mixScaleX2 = readFloat(input); + float mixScaleY2 = readFloat(input); + float mixShearY2 = readFloat(input); + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); + setBezier(input, timeline, bezier++, frame, 3, time, time2, mixScaleX, mixScaleX2, 1); + setBezier(input, timeline, bezier++, frame, 4, time, time2, mixScaleY, mixScaleY2, 1); + setBezier(input, timeline, bezier++, frame, 5, time, time2, mixShearY, mixShearY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + mixScaleX = mixScaleX2; + mixScaleY = mixScaleY2; + mixShearY = mixShearY2; + } + timelines.add(timeline); + } + + // Path constraint timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + int index = readVarint(input, true); + PathConstraintData *data = skeletonData->_pathConstraints[index]; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ii++) { + int type = readByte(input); + int frameCount = readVarint(input, true); + int bezierCount = readVarint(input, true); + switch (type) { + case PATH_POSITION: { + readTimeline(input, timelines, new PathConstraintPositionTimeline(frameCount, bezierCount, index), + data->_positionMode == PositionMode_Fixed ? scale : 1); + break; + } + case PATH_SPACING: { + readTimeline(input, timelines, + new PathConstraintSpacingTimeline(frameCount, + bezierCount, + index), + data->_spacingMode == SpacingMode_Length || + data->_spacingMode == SpacingMode_Fixed + ? scale + : 1); + break; + } + case PATH_MIX: + PathConstraintMixTimeline *timeline = new PathConstraintMixTimeline(frameCount, bezierCount, index); + float time = readFloat(input); + float mixRotate = readFloat(input); + float mixX = readFloat(input); + float mixY = readFloat(input); + for (int frame = 0, bezier = 0, frameLast = (int) timeline->getFrameCount() - 1;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY); + if (frame == frameLast) break; + float time2 = readFloat(input); + float mixRotate2 = readFloat(input); + float mixX2 = readFloat(input); + float mixY2 = readFloat(input); + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, mixRotate, mixRotate2, 1); + setBezier(input, timeline, bezier++, frame, 1, time, time2, mixX, mixX2, 1); + setBezier(input, timeline, bezier++, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + } + timelines.add(timeline); + } + } + } + + // Physics timelines. + for (int i = 0, n = readVarint(input, true); i < n; i++) { + int index = readVarint(input, true) - 1; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ii++) { + int type = readByte(input); + int frameCount = readVarint(input, true); + if (type == PHYSICS_RESET) { + PhysicsConstraintResetTimeline *timeline = new (__FILE__, __LINE__) PhysicsConstraintResetTimeline(frameCount, index); + for (int frame = 0; frame < frameCount; frame++) + timeline->setFrame(frame, readFloat(input)); + timelines.add(timeline); + continue; + } + int bezierCount = readVarint(input, true); + switch (type) { + case PHYSICS_INERTIA: + readTimeline(input, timelines, new PhysicsConstraintInertiaTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_STRENGTH: + readTimeline(input, timelines, new PhysicsConstraintStrengthTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_DAMPING: + readTimeline(input, timelines, new PhysicsConstraintDampingTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MASS: + readTimeline(input, timelines, new PhysicsConstraintMassTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_WIND: + readTimeline(input, timelines, new PhysicsConstraintWindTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_GRAVITY: + readTimeline(input, timelines, new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index), 1); + break; + case PHYSICS_MIX: + readTimeline(input, timelines, new PhysicsConstraintMixTimeline(frameCount, bezierCount, index), 1); + } + } + } + + // Attachment timelines. + for (int i = 0, n = readVarint(input, true); i < n; ++i) { + Skin *skin = skeletonData->_skins[readVarint(input, true)]; + for (int ii = 0, nn = readVarint(input, true); ii < nn; ++ii) { + int slotIndex = readVarint(input, true); + for (int iii = 0, nnn = readVarint(input, true); iii < nnn; iii++) { + const char *attachmentName = readStringRef(input, skeletonData); + Attachment *baseAttachment = skin->getAttachment(slotIndex, String(attachmentName)); + if (!baseAttachment) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError("Attachment not found: ", attachmentName); + return NULL; + } + unsigned int timelineType = readByte(input); + int frameCount = readVarint(input, true); + int frameLast = frameCount - 1; + + switch (timelineType) { + case ATTACHMENT_DEFORM: { + VertexAttachment *attachment = static_cast(baseAttachment); + bool weighted = attachment->_bones.size() > 0; + Vector &vertices = attachment->_vertices; + int deformLength = weighted ? (int) vertices.size() / 3 * 2 : (int) vertices.size(); + + int bezierCount = readVarint(input, true); + DeformTimeline *timeline = new (__FILE__, __LINE__) DeformTimeline(frameCount, bezierCount, slotIndex, + attachment); + + float time = readFloat(input); + for (int frame = 0, bezier = 0;; ++frame) { + Vector deform; + size_t end = (size_t) readVarint(input, true); + if (end == 0) { + if (weighted) { + deform.setSize(deformLength, 0); + for (int iiii = 0; iiii < deformLength; ++iiii) + deform[iiii] = 0; + } else { + deform.clearAndAddAll(vertices); + } + } else { + deform.setSize(deformLength, 0); + size_t start = (size_t) readVarint(input, true); + end += start; + if (scale == 1) { + for (size_t v = start; v < end; ++v) + deform[v] = readFloat(input); + } else { + for (size_t v = start; v < end; ++v) + deform[v] = readFloat(input) * scale; + } + + if (!weighted) { + for (size_t v = 0, vn = deform.size(); v < vn; ++v) + deform[v] += vertices[v]; + } + } + + timeline->setFrame(frame, time, deform); + if (frame == frameLast) break; + float time2 = readFloat(input); + switch (readSByte(input)) { + case CURVE_STEPPED: + timeline->setStepped(frame); + break; + case CURVE_BEZIER: + setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + } + + timelines.add(timeline); + break; + } + case ATTACHMENT_SEQUENCE: { + SequenceTimeline *timeline = new (__FILE__, __LINE__) SequenceTimeline(frameCount, slotIndex, baseAttachment); + for (int frame = 0; frame < frameCount; frame++) { + float time = readFloat(input); + int modeAndIndex = readInt(input); + float delay = readFloat(input); + timeline->setFrame(frame, time, (spine::SequenceMode)(modeAndIndex & 0xf), modeAndIndex >> 4, delay); + } + timelines.add(timeline); + break; + } + } + } + } + } + + // Draw order timeline. + size_t drawOrderCount = (size_t) readVarint(input, true); + if (drawOrderCount > 0) { + DrawOrderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderTimeline(drawOrderCount); + + size_t slotCount = skeletonData->_slots.size(); + for (size_t i = 0; i < drawOrderCount; ++i) { + float time = readFloat(input); + size_t offsetCount = (size_t) readVarint(input, true); + + Vector drawOrder; + drawOrder.setSize(slotCount, 0); + for (int ii = (int) slotCount - 1; ii >= 0; --ii) + drawOrder[ii] = -1; + + Vector unchanged; + unchanged.setSize(slotCount - offsetCount, 0); + size_t originalIndex = 0, unchangedIndex = 0; + for (size_t ii = 0; ii < offsetCount; ++ii) { + size_t slotIndex = (size_t) readVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = (int) originalIndex++; + // Set changed items. + size_t index = originalIndex; + drawOrder[index + (size_t) readVarint(input, true)] = (int) originalIndex++; + } + + // Collect remaining unchanged items. + while (originalIndex < slotCount) { + unchanged[unchangedIndex++] = (int) originalIndex++; + } + + // Fill in unchanged items. + for (int ii = (int) slotCount - 1; ii >= 0; --ii) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline->setFrame(i, time, drawOrder); + } + timelines.add(timeline); + } + + // Event timeline. + int eventCount = readVarint(input, true); + if (eventCount > 0) { + EventTimeline *timeline = new (__FILE__, __LINE__) EventTimeline(eventCount); + + for (int i = 0; i < eventCount; ++i) { + float time = readFloat(input); + EventData *eventData = skeletonData->_events[readVarint(input, true)]; + Event *event = new (__FILE__, __LINE__) Event(time, *eventData); + + event->_intValue = readVarint(input, false); + event->_floatValue = readFloat(input); + const char *event_stringValue = readString(input); + if (event_stringValue == nullptr) { + event->_stringValue = eventData->_stringValue; + } else { + event->_stringValue = String(event_stringValue); + SpineExtension::free(event_stringValue, __FILE__, __LINE__); + } + + if (!eventData->_audioPath.isEmpty()) { + event->_volume = readFloat(input); + event->_balance = readFloat(input); + } + timeline->setFrame(i, event); + } + timelines.add(timeline); + } + + float duration = 0; + for (int i = 0, n = (int) timelines.size(); i < n; i++) { + duration = MathUtil::max(duration, (timelines[i])->getDuration()); + } + return new (__FILE__, __LINE__) Animation(String(name), timelines, duration); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonBounds.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonBounds.cpp new file mode 100644 index 0000000..5681af4 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonBounds.cpp @@ -0,0 +1,231 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include + +#include + +#include + +using namespace spine; + +SkeletonBounds::SkeletonBounds() : _minX(0), _minY(0), _maxX(0), _maxY(0) { +} + +SkeletonBounds::~SkeletonBounds() { + for (size_t i = 0, n = _polygons.size(); i < n; i++) + _polygonPool.free(_polygons[i]); + _polygons.clear(); +} + +void SkeletonBounds::update(Skeleton &skeleton, bool updateAabb) { + Vector &slots = skeleton.getSlots(); + size_t slotCount = slots.size(); + + _boundingBoxes.clear(); + for (size_t i = 0, n = _polygons.size(); i < n; ++i) { + _polygonPool.free(_polygons[i]); + } + + _polygons.clear(); + + for (size_t i = 0; i < slotCount; i++) { + Slot *slot = slots[i]; + if (!slot->getBone().isActive()) continue; + + Attachment *attachment = slot->getAttachment(); + if (attachment == NULL || !attachment->getRTTI().instanceOf(BoundingBoxAttachment::rtti)) continue; + BoundingBoxAttachment *boundingBox = static_cast(attachment); + _boundingBoxes.add(boundingBox); + + spine::Polygon *polygonP = _polygonPool.obtain(); + _polygons.add(polygonP); + + Polygon &polygon = *polygonP; + + size_t count = boundingBox->getWorldVerticesLength(); + polygon._count = (int) count; + if (polygon._vertices.size() < count) { + polygon._vertices.setSize(count, 0); + } + boundingBox->computeWorldVertices(*slot, polygon._vertices); + } + + if (updateAabb) + aabbCompute(); + else { + _minX = FLT_MIN; + _minY = FLT_MIN; + _maxX = FLT_MAX; + _maxY = FLT_MAX; + } +} + +bool SkeletonBounds::aabbcontainsPoint(float x, float y) { + return x >= _minX && x <= _maxX && y >= _minY && y <= _maxY; +} + +bool SkeletonBounds::aabbintersectsSegment(float x1, float y1, float x2, float y2) { + float minX = _minX; + float minY = _minY; + float maxX = _maxX; + float maxY = _maxY; + + if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || + (y1 >= maxY && y2 >= maxY)) { + return false; + } + + float m = (y2 - y1) / (x2 - x1); + float y = m * (minX - x1) + y1; + if (y > minY && y < maxY) return true; + y = m * (maxX - x1) + y1; + if (y > minY && y < maxY) return true; + float x = (minY - y1) / m + x1; + if (x > minX && x < maxX) return true; + x = (maxY - y1) / m + x1; + if (x > minX && x < maxX) return true; + return false; +} + +bool SkeletonBounds::aabbIntersectsSkeleton(SkeletonBounds &bounds) { + return _minX < bounds._maxX && _maxX > bounds._minX && _minY < bounds._maxY && _maxY > bounds._minY; +} + +bool SkeletonBounds::containsPoint(spine::Polygon *polygon, float x, float y) { + Vector &vertices = polygon->_vertices; + int nn = polygon->_count; + + int prevIndex = nn - 2; + bool inside = false; + for (int ii = 0; ii < nn; ii += 2) { + float vertexY = vertices[ii + 1]; + float prevY = vertices[prevIndex + 1]; + if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) { + float vertexX = vertices[ii]; + if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) { + inside = !inside; + } + } + prevIndex = ii; + } + return inside; +} + +BoundingBoxAttachment *SkeletonBounds::containsPoint(float x, float y) { + for (size_t i = 0, n = _polygons.size(); i < n; ++i) + if (containsPoint(_polygons[i], x, y)) return _boundingBoxes[i]; + return NULL; +} + +BoundingBoxAttachment *SkeletonBounds::intersectsSegment(float x1, float y1, float x2, float y2) { + for (size_t i = 0, n = _polygons.size(); i < n; ++i) + if (intersectsSegment(_polygons[i], x1, y1, x2, y2)) return _boundingBoxes[i]; + return NULL; +} + +bool SkeletonBounds::intersectsSegment(spine::Polygon *polygon, float x1, float y1, float x2, float y2) { + Vector &vertices = polygon->_vertices; + size_t nn = polygon->_count; + + float width12 = x1 - x2, height12 = y1 - y2; + float det1 = x1 * y2 - y1 * x2; + float x3 = vertices[nn - 2], y3 = vertices[nn - 1]; + for (size_t ii = 0; ii < nn; ii += 2) { + float x4 = vertices[ii], y4 = vertices[ii + 1]; + float det2 = x3 * y4 - y3 * x4; + float width34 = x3 - x4, height34 = y3 - y4; + float det3 = width12 * height34 - height12 * width34; + float x = (det1 * width34 - width12 * det2) / det3; + if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) { + float y = (det1 * height34 - height12 * det2) / det3; + if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) { + return true; + } + } + x3 = x4; + y3 = y4; + } + + return false; +} + +spine::Polygon *SkeletonBounds::getPolygon(BoundingBoxAttachment *attachment) { + int index = _boundingBoxes.indexOf(attachment); + return index == -1 ? NULL : _polygons[index]; +} + +BoundingBoxAttachment *SkeletonBounds::getBoundingBox(Polygon *polygon) { + int index = _polygons.indexOf(polygon); + return index == -1 ? NULL : _boundingBoxes[index]; +} + +Vector &SkeletonBounds::getPolygons() { + return _polygons; +} + +Vector &SkeletonBounds::getBoundingBoxes() { + return _boundingBoxes; +} + +float SkeletonBounds::getWidth() { + return _maxX - _minX; +} + +float SkeletonBounds::getHeight() { + return _maxY - _minY; +} + +void SkeletonBounds::aabbCompute() { + float minX = FLT_MAX; + float minY = FLT_MAX; + float maxX = FLT_MIN; + float maxY = FLT_MIN; + + for (size_t i = 0, n = _polygons.size(); i < n; ++i) { + spine::Polygon *polygon = _polygons[i]; + Vector &vertices = polygon->_vertices; + for (int ii = 0, nn = polygon->_count; ii < nn; ii += 2) { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = MathUtil::min(minX, x); + minY = MathUtil::min(minY, y); + maxX = MathUtil::max(maxX, x); + maxY = MathUtil::max(maxY, y); + } + } + _minX = minX; + _minY = minY; + _maxX = maxX; + _maxY = maxY; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonClipping.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonClipping.cpp new file mode 100644 index 0000000..fbe09ed --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonClipping.cpp @@ -0,0 +1,397 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +using namespace spine; + +SkeletonClipping::SkeletonClipping() : _clipAttachment(NULL) { + _clipOutput.ensureCapacity(128); + _clippedVertices.ensureCapacity(128); + _clippedTriangles.ensureCapacity(128); + _clippedUVs.ensureCapacity(128); +} + +size_t SkeletonClipping::clipStart(Slot &slot, ClippingAttachment *clip) { + if (_clipAttachment != NULL) { + return 0; + } + + _clipAttachment = clip; + + int n = (int) clip->getWorldVerticesLength(); + _clippingPolygon.setSize(n, 0); + clip->computeWorldVertices(slot, 0, n, _clippingPolygon, 0, 2); + makeClockwise(_clippingPolygon); + _clippingPolygons = &_triangulator.decompose(_clippingPolygon, _triangulator.triangulate(_clippingPolygon)); + + for (size_t i = 0; i < _clippingPolygons->size(); ++i) { + Vector *polygonP = (*_clippingPolygons)[i]; + Vector &polygon = *polygonP; + makeClockwise(polygon); + polygon.add(polygon[0]); + polygon.add(polygon[1]); + } + + return (*_clippingPolygons).size(); +} + +void SkeletonClipping::clipEnd(Slot &slot) { + if (_clipAttachment != NULL && _clipAttachment->_endSlot == &slot._data) { + clipEnd(); + } +} + +void SkeletonClipping::clipEnd() { + if (_clipAttachment == NULL) return; + + _clipAttachment = NULL; + _clippingPolygons = NULL; + _clippedVertices.clear(); + _clippedUVs.clear(); + _clippedTriangles.clear(); + _clippingPolygon.clear(); +} + +void SkeletonClipping::clipTriangles(float *vertices, unsigned short *triangles, + size_t trianglesLength) { + Vector &clipOutput = _clipOutput; + Vector &clippedVertices = _clippedVertices; + Vector &clippedTriangles = _clippedTriangles; + Vector *> &polygons = *_clippingPolygons; + size_t polygonsCount = (*_clippingPolygons).size(); + + size_t index = 0; + clippedVertices.clear(); + _clippedUVs.clear(); + clippedTriangles.clear(); + + int stride = 2; + size_t i = 0; +continue_outer: + for (; i < trianglesLength; i += 3) { + int vertexOffset = triangles[i] * stride; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] * stride; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] * stride; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + + for (size_t p = 0; p < polygonsCount; p++) { + size_t s = clippedVertices.size(); + if (clip(x1, y1, x2, y2, x3, y3, &(*polygons[p]), &clipOutput)) { + size_t clipOutputLength = clipOutput.size(); + if (clipOutputLength == 0) continue; + + size_t clipOutputCount = clipOutputLength >> 1; + clippedVertices.setSize(s + clipOutputCount * 2, 0); + for (size_t ii = 0; ii < clipOutputLength; ii += 2) { + float x = clipOutput[ii], y = clipOutput[ii + 1]; + clippedVertices[s] = x; + clippedVertices[s + 1] = y; + s += 2; + } + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3 * (clipOutputCount - 2), 0); + clipOutputCount--; + for (size_t ii = 1; ii < clipOutputCount; ii++) { + clippedTriangles[s] = (unsigned short) (index); + clippedTriangles[s + 1] = (unsigned short) (index + ii); + clippedTriangles[s + 2] = (unsigned short) (index + ii + 1); + s += 3; + } + index += clipOutputCount + 1; + } else { + clippedVertices.setSize(s + 3 * 2, 0); + clippedVertices[s] = x1; + clippedVertices[s + 1] = y1; + clippedVertices[s + 2] = x2; + clippedVertices[s + 3] = y2; + clippedVertices[s + 4] = x3; + clippedVertices[s + 5] = y3; + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3, 0); + clippedTriangles[s] = (unsigned short) index; + clippedTriangles[s + 1] = (unsigned short) (index + 1); + clippedTriangles[s + 2] = (unsigned short) (index + 2); + index += 3; + i += 3; + goto continue_outer; + } + } + } +} + +void SkeletonClipping::clipTriangles(Vector &vertices, Vector &triangles, Vector &uvs, + size_t stride) { + clipTriangles(vertices.buffer(), triangles.buffer(), triangles.size(), uvs.buffer(), stride); +} + +void SkeletonClipping::clipTriangles(float *vertices, unsigned short *triangles, + size_t trianglesLength, float *uvs, size_t stride) { + Vector &clipOutput = _clipOutput; + Vector &clippedVertices = _clippedVertices; + Vector &clippedTriangles = _clippedTriangles; + Vector *> &polygons = *_clippingPolygons; + size_t polygonsCount = (*_clippingPolygons).size(); + + size_t index = 0; + clippedVertices.clear(); + _clippedUVs.clear(); + clippedTriangles.clear(); + + size_t i = 0; +continue_outer: + for (; i < trianglesLength; i += 3) { + int vertexOffset = triangles[i] * (int) stride; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] * (int) stride; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] * (int) stride; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (size_t p = 0; p < polygonsCount; p++) { + size_t s = clippedVertices.size(); + if (clip(x1, y1, x2, y2, x3, y3, &(*polygons[p]), &clipOutput)) { + size_t clipOutputLength = clipOutput.size(); + if (clipOutputLength == 0) continue; + float d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1; + float d = 1 / (d0 * d2 + d1 * (y1 - y3)); + + size_t clipOutputCount = clipOutputLength >> 1; + clippedVertices.setSize(s + clipOutputCount * 2, 0); + _clippedUVs.setSize(s + clipOutputCount * 2, 0); + for (size_t ii = 0; ii < clipOutputLength; ii += 2) { + float x = clipOutput[ii], y = clipOutput[ii + 1]; + clippedVertices[s] = x; + clippedVertices[s + 1] = y; + float c0 = x - x3, c1 = y - y3; + float a = (d0 * c0 + d1 * c1) * d; + float b = (d4 * c0 + d2 * c1) * d; + float c = 1 - a - b; + _clippedUVs[s] = u1 * a + u2 * b + u3 * c; + _clippedUVs[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3 * (clipOutputCount - 2), 0); + clipOutputCount--; + for (size_t ii = 1; ii < clipOutputCount; ii++) { + clippedTriangles[s] = (unsigned short) (index); + clippedTriangles[s + 1] = (unsigned short) (index + ii); + clippedTriangles[s + 2] = (unsigned short) (index + ii + 1); + s += 3; + } + index += clipOutputCount + 1; + } else { + clippedVertices.setSize(s + 3 * 2, 0); + _clippedUVs.setSize(s + 3 * 2, 0); + clippedVertices[s] = x1; + clippedVertices[s + 1] = y1; + clippedVertices[s + 2] = x2; + clippedVertices[s + 3] = y2; + clippedVertices[s + 4] = x3; + clippedVertices[s + 5] = y3; + + _clippedUVs[s] = u1; + _clippedUVs[s + 1] = v1; + _clippedUVs[s + 2] = u2; + _clippedUVs[s + 3] = v2; + _clippedUVs[s + 4] = u3; + _clippedUVs[s + 5] = v3; + + s = clippedTriangles.size(); + clippedTriangles.setSize(s + 3, 0); + clippedTriangles[s] = (unsigned short) index; + clippedTriangles[s + 1] = (unsigned short) (index + 1); + clippedTriangles[s + 2] = (unsigned short) (index + 2); + index += 3; + i += 3; + goto continue_outer; + } + } + } +} + +bool SkeletonClipping::isClipping() { + return _clipAttachment != NULL; +} + +Vector &SkeletonClipping::getClippedVertices() { + return _clippedVertices; +} + +Vector &SkeletonClipping::getClippedTriangles() { + return _clippedTriangles; +} + +Vector &SkeletonClipping::getClippedUVs() { + return _clippedUVs; +} + +bool SkeletonClipping::clip(float x1, float y1, float x2, float y2, float x3, float y3, Vector *clippingArea, + Vector *output) { + Vector *originalOutput = output; + bool clipped = false; + + // Avoid copy at the end. + Vector *input; + if (clippingArea->size() % 4 >= 2) { + input = output; + output = &_scratch; + } else + input = &_scratch; + + input->clear(); + input->add(x1); + input->add(y1); + input->add(x2); + input->add(y2); + input->add(x3); + input->add(y3); + input->add(x1); + input->add(y1); + output->clear(); + + size_t clippingVerticesLast = clippingArea->size() - 4; + Vector &clippingVertices = *clippingArea; + for (size_t i = 0;; i += 2) { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float ex = edgeX - clippingVertices[i + 2], ey = edgeY - clippingVertices[i + 3]; + + size_t outputStart = output->size(); + Vector &inputVertices = *input; + for (size_t ii = 0, nn = input->size() - 2; ii < nn;) { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + ii += 2; + float inputX2 = inputVertices[ii], inputY2 = inputVertices[ii + 1]; + float s2 = ey * (edgeX - inputX2) > ex * (edgeY - inputY2); + float s1 = ey * (edgeX - inputX) - ex * (edgeY - inputY); + if (s1 > 0) { + if (s2) {// v1 inside, v2 inside + output->add(inputX2); + output->add(inputY2); + continue; + } + // v1 inside, v2 outside + float ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + output->add(inputX + ix * t); + output->add(inputY + iy * t); + } else { + output->add(inputX2); + output->add(inputY2); + } + } else if (s2) {// v1 outside, v2 inside + float ix = inputX2 - inputX, iy = inputY2 - inputY, t = s1 / (ix * ey - iy * ex); + if (t >= 0 && t <= 1) { + output->add(inputX + ix * t); + output->add(inputY + iy * t); + output->add(inputX2); + output->add(inputY2); + } else { + output->add(inputX2); + output->add(inputY2); + continue; + } + } + clipped = true; + } + + + if (outputStart == output->size()) { + // All edges outside. + originalOutput->clear(); + return true; + } + + output->add((*output)[0]); + output->add((*output)[1]); + + if (i == clippingVerticesLast) { + break; + } + Vector *temp = output; + output = input; + output->clear(); + input = temp; + } + + if (originalOutput != output) { + originalOutput->clear(); + for (size_t i = 0, n = output->size() - 2; i < n; ++i) + originalOutput->add((*output)[i]); + } else + originalOutput->setSize(originalOutput->size() - 2, 0); + + if (originalOutput->size() < 6) { + originalOutput->clear(); + return false; + } + return clipped; +} + +void SkeletonClipping::makeClockwise(Vector &polygon) { + size_t verticeslength = polygon.size(); + + float area = polygon[verticeslength - 2] * polygon[1] - polygon[0] * polygon[verticeslength - 1]; + float p1x, p1y, p2x, p2y; + + for (size_t i = 0, n = verticeslength - 3; i < n; i += 2) { + p1x = polygon[i]; + p1y = polygon[i + 1]; + p2x = polygon[i + 2]; + p2y = polygon[i + 3]; + area += p1x * p2y - p2x * p1y; + } + + if (area < 0) return; + + for (size_t i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { + float x = polygon[i], y = polygon[i + 1]; + int other = (int) (lastX - i); + polygon[i] = polygon[other]; + polygon[i + 1] = polygon[other + 1]; + polygon[other] = x; + polygon[other + 1] = y; + } +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonData.cpp new file mode 100644 index 0000000..e3b35f0 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonData.cpp @@ -0,0 +1,244 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace spine; + +SkeletonData::SkeletonData() : _name(), + _defaultSkin(NULL), + _x(0), + _y(0), + _width(0), + _height(0), + _referenceScale(100), + _version(), + _hash(), + _fps(0), + _imagesPath() { +} + +SkeletonData::~SkeletonData() { + ContainerUtil::cleanUpVectorOfPointers(_bones); + ContainerUtil::cleanUpVectorOfPointers(_slots); + ContainerUtil::cleanUpVectorOfPointers(_skins); + + _defaultSkin = NULL; + + ContainerUtil::cleanUpVectorOfPointers(_events); + ContainerUtil::cleanUpVectorOfPointers(_animations); + ContainerUtil::cleanUpVectorOfPointers(_ikConstraints); + ContainerUtil::cleanUpVectorOfPointers(_transformConstraints); + ContainerUtil::cleanUpVectorOfPointers(_pathConstraints); + ContainerUtil::cleanUpVectorOfPointers(_physicsConstraints); + for (size_t i = 0; i < _strings.size(); i++) { + SpineExtension::free(_strings[i], __FILE__, __LINE__); + } +} + +BoneData *SkeletonData::findBone(const String &boneName) { + return ContainerUtil::findWithName(_bones, boneName); +} + +SlotData *SkeletonData::findSlot(const String &slotName) { + return ContainerUtil::findWithName(_slots, slotName); +} + +Skin *SkeletonData::findSkin(const String &skinName) { + return ContainerUtil::findWithName(_skins, skinName); +} + +spine::EventData *SkeletonData::findEvent(const String &eventDataName) { + return ContainerUtil::findWithName(_events, eventDataName); +} + +Animation *SkeletonData::findAnimation(const String &animationName) { + return ContainerUtil::findWithName(_animations, animationName); +} + +IkConstraintData *SkeletonData::findIkConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_ikConstraints, constraintName); +} + +TransformConstraintData *SkeletonData::findTransformConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_transformConstraints, constraintName); +} + +PathConstraintData *SkeletonData::findPathConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_pathConstraints, constraintName); +} + +PhysicsConstraintData *SkeletonData::findPhysicsConstraint(const String &constraintName) { + return ContainerUtil::findWithName(_physicsConstraints, constraintName); +} + +const String &SkeletonData::getName() { + return _name; +} + +void SkeletonData::setName(const String &inValue) { + _name = inValue; +} + +Vector &SkeletonData::getBones() { + return _bones; +} + +Vector &SkeletonData::getSlots() { + return _slots; +} + +Vector &SkeletonData::getSkins() { + return _skins; +} + +Skin *SkeletonData::getDefaultSkin() { + return _defaultSkin; +} + +void SkeletonData::setDefaultSkin(Skin *inValue) { + _defaultSkin = inValue; +} + +Vector &SkeletonData::getEvents() { + return _events; +} + +Vector &SkeletonData::getAnimations() { + return _animations; +} + +Vector &SkeletonData::getIkConstraints() { + return _ikConstraints; +} + +Vector &SkeletonData::getTransformConstraints() { + return _transformConstraints; +} + +Vector &SkeletonData::getPathConstraints() { + return _pathConstraints; +} + +Vector &SkeletonData::getPhysicsConstraints() { + return _physicsConstraints; +} + +float SkeletonData::getX() { + return _x; +} + +void SkeletonData::setX(float inValue) { + _x = inValue; +} + +float SkeletonData::getY() { + return _y; +} + +void SkeletonData::setY(float inValue) { + _y = inValue; +} + +float SkeletonData::getWidth() { + return _width; +} + +void SkeletonData::setWidth(float inValue) { + _width = inValue; +} + +float SkeletonData::getHeight() { + return _height; +} + +void SkeletonData::setHeight(float inValue) { + _height = inValue; +} + +float SkeletonData::getReferenceScale() { + return _referenceScale; +} + +void SkeletonData::setReferenceScale(float inValue) { + _referenceScale = inValue; +} + +const String &SkeletonData::getVersion() { + return _version; +} + +void SkeletonData::setVersion(const String &inValue) { + _version = inValue; +} + +const String &SkeletonData::getHash() { + return _hash; +} + +void SkeletonData::setHash(const String &inValue) { + _hash = inValue; +} + +const String &SkeletonData::getImagesPath() { + return _imagesPath; +} + +void SkeletonData::setImagesPath(const String &inValue) { + _imagesPath = inValue; +} + + +const String &SkeletonData::getAudioPath() { + return _audioPath; +} + +void SkeletonData::setAudioPath(const String &inValue) { + _audioPath = inValue; +} + +float SkeletonData::getFps() { + return _fps; +} + +void SkeletonData::setFps(float inValue) { + _fps = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonJson.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonJson.cpp new file mode 100644 index 0000000..4ad0b92 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonJson.cpp @@ -0,0 +1,1617 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +static float toColor(const char *value, size_t index) { + char digits[3]; + char *error; + int color; + + if (index >= strlen(value) / 2) return -1; + + value += index * 2; + + digits[0] = *value; + digits[1] = *(value + 1); + digits[2] = '\0'; + color = (int) strtoul(digits, &error, 16); + if (*error != 0) return -1; + + return color / (float) 255; +} + +static void toColor(Color &color, const char *value, bool hasAlpha) { + color.r = toColor(value, 0); + color.g = toColor(value, 1); + color.b = toColor(value, 2); + if (hasAlpha) color.a = toColor(value, 3); +} + +SkeletonJson::SkeletonJson(Atlas *atlas) : _attachmentLoader(new (__FILE__, __LINE__) AtlasAttachmentLoader(atlas)), + _scale(1), _ownsLoader(true) {} + +SkeletonJson::SkeletonJson(AttachmentLoader *attachmentLoader, bool ownsLoader) : _attachmentLoader(attachmentLoader), + _scale(1), + _ownsLoader(ownsLoader) { + assert(_attachmentLoader != NULL); +} + +SkeletonJson::~SkeletonJson() { + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + + if (_ownsLoader) delete _attachmentLoader; +} + +SkeletonData *SkeletonJson::readSkeletonDataFile(const String &path) { + int length; + SkeletonData *skeletonData; + const char *json = SpineExtension::readFile(path, &length); + if (length == 0 || !json) { + setError(NULL, "Unable to read skeleton file: ", path); + return NULL; + } + + skeletonData = readSkeletonData(json); + + SpineExtension::free(json, __FILE__, __LINE__); + + return skeletonData; +} + +SkeletonData *SkeletonJson::readSkeletonData(const char *json) { + int i, ii; + SkeletonData *skeletonData; + Json *root, *skeleton, *bones, *boneMap, *ik, *transform, *path, *physics, *slots, *skins, *animations, *events; + + _error = ""; + _linkedMeshes.clear(); + + root = new (__FILE__, __LINE__) Json(json); + + if (!root) { + setError(NULL, "Invalid skeleton JSON: ", Json::getError()); + return NULL; + } + + skeletonData = new (__FILE__, __LINE__) SkeletonData(); + + skeleton = Json::getItem(root, "skeleton"); + if (skeleton) { + skeletonData->_hash = Json::getString(skeleton, "hash", 0); + skeletonData->_version = Json::getString(skeleton, "spine", 0); + if (!skeletonData->_version.startsWith(SPINE_VERSION_STRING)) { + char errorMsg[255]; + snprintf(errorMsg, 255, "Skeleton version %s does not match runtime version %s", skeletonData->_version.buffer(), SPINE_VERSION_STRING); + delete skeletonData; + setError(NULL, errorMsg, ""); + return NULL; + } + skeletonData->_x = Json::getFloat(skeleton, "x", 0); + skeletonData->_y = Json::getFloat(skeleton, "y", 0); + skeletonData->_width = Json::getFloat(skeleton, "width", 0); + skeletonData->_height = Json::getFloat(skeleton, "height", 0); + skeletonData->_referenceScale = Json::getFloat(skeleton, "referenceScale", 100) * _scale; + skeletonData->_fps = Json::getFloat(skeleton, "fps", 30); + skeletonData->_audioPath = Json::getString(skeleton, "audio", 0); + skeletonData->_imagesPath = Json::getString(skeleton, "images", 0); + } + + /* Bones. */ + bones = Json::getItem(root, "bones"); + skeletonData->_bones.setSize(bones->_size, 0); + int bonesCount = 0; + for (boneMap = bones->_child, i = 0; boneMap; boneMap = boneMap->_next, ++i) { + BoneData *data; + const char *inherit; + + BoneData *parent = 0; + const char *parentName = Json::getString(boneMap, "parent", 0); + if (parentName) { + parent = skeletonData->findBone(parentName); + if (!parent) { + delete skeletonData; + setError(root, "Parent bone not found: ", parentName); + return NULL; + } + } + + data = new (__FILE__, __LINE__) BoneData(bonesCount, Json::getString(boneMap, "name", 0), parent); + + data->_length = Json::getFloat(boneMap, "length", 0) * _scale; + data->_x = Json::getFloat(boneMap, "x", 0) * _scale; + data->_y = Json::getFloat(boneMap, "y", 0) * _scale; + data->_rotation = Json::getFloat(boneMap, "rotation", 0); + data->_scaleX = Json::getFloat(boneMap, "scaleX", 1); + data->_scaleY = Json::getFloat(boneMap, "scaleY", 1); + data->_shearX = Json::getFloat(boneMap, "shearX", 0); + data->_shearY = Json::getFloat(boneMap, "shearY", 0); + inherit = Json::getString(boneMap, "inherit", "normal"); + data->_inherit = Inherit_Normal; + if (strcmp(inherit, "normal") == 0) data->_inherit = Inherit_Normal; + else if (strcmp(inherit, "onlyTranslation") == 0) + data->_inherit = Inherit_OnlyTranslation; + else if (strcmp(inherit, "noRotationOrReflection") == 0) + data->_inherit = Inherit_NoRotationOrReflection; + else if (strcmp(inherit, "noScale") == 0) + data->_inherit = Inherit_NoScale; + else if (strcmp(inherit, "noScaleOrReflection") == 0) + data->_inherit = Inherit_NoScaleOrReflection; + data->_skinRequired = Json::getBoolean(boneMap, "skin", false); + + const char *color = Json::getString(boneMap, "color", NULL); + if (color) toColor(data->getColor(), color, true); + + data->_icon = Json::getString(boneMap, "icon", ""); + data->_visible = Json::getBoolean(boneMap, "visible", true); + + skeletonData->_bones[i] = data; + bonesCount++; + } + + /* Slots. */ + slots = Json::getItem(root, "slots"); + if (slots) { + Json *slotMap; + skeletonData->_slots.ensureCapacity(slots->_size); + skeletonData->_slots.setSize(slots->_size, 0); + for (slotMap = slots->_child, i = 0; slotMap; slotMap = slotMap->_next, ++i) { + SlotData *data; + const char *color; + const char *dark; + Json *item; + + const char *boneName = Json::getString(slotMap, "bone", 0); + BoneData *boneData = skeletonData->findBone(boneName); + if (!boneData) { + delete skeletonData; + setError(root, "Slot bone not found: ", boneName); + return NULL; + } + + String slotName = String(Json::getString(slotMap, "name", 0)); + data = new (__FILE__, __LINE__) SlotData(i, slotName, *boneData); + + color = Json::getString(slotMap, "color", 0); + if (color) { + Color &c = data->getColor(); + c.r = toColor(color, 0); + c.g = toColor(color, 1); + c.b = toColor(color, 2); + c.a = toColor(color, 3); + } + + dark = Json::getString(slotMap, "dark", 0); + if (dark) { + Color &darkColor = data->getDarkColor(); + darkColor.r = toColor(dark, 0); + darkColor.g = toColor(dark, 1); + darkColor.b = toColor(dark, 2); + darkColor.a = 1; + data->setHasDarkColor(true); + } + + item = Json::getItem(slotMap, "attachment"); + if (item) data->setAttachmentName(item->_valueString); + + item = Json::getItem(slotMap, "blend"); + if (item) { + if (strcmp(item->_valueString, "additive") == 0) data->_blendMode = BlendMode_Additive; + else if (strcmp(item->_valueString, "multiply") == 0) + data->_blendMode = BlendMode_Multiply; + else if (strcmp(item->_valueString, "screen") == 0) + data->_blendMode = BlendMode_Screen; + } + data->_visible = Json::getBoolean(slotMap, "visible", true); + skeletonData->_slots[i] = data; + } + } + + /* IK constraints. */ + ik = Json::getItem(root, "ik"); + if (ik) { + Json *constraintMap; + skeletonData->_ikConstraints.ensureCapacity(ik->_size); + skeletonData->_ikConstraints.setSize(ik->_size, 0); + for (constraintMap = ik->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *targetName; + + IkConstraintData *data = new (__FILE__, __LINE__) IkConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "IK bone not found: ", boneMap->_valueString); + return NULL; + } + } + + targetName = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findBone(targetName); + if (!data->_target) { + delete skeletonData; + setError(root, "Target bone not found: ", targetName); + return NULL; + } + + data->_mix = Json::getFloat(constraintMap, "mix", 1); + data->_softness = Json::getFloat(constraintMap, "softness", 0) * _scale; + data->_bendDirection = Json::getInt(constraintMap, "bendPositive", 1) ? 1 : -1; + data->_compress = Json::getInt(constraintMap, "compress", 0) ? true : false; + data->_stretch = Json::getInt(constraintMap, "stretch", 0) ? true : false; + data->_uniform = Json::getInt(constraintMap, "uniform", 0) ? true : false; + + skeletonData->_ikConstraints[i] = data; + } + } + + /* Transform constraints. */ + transform = Json::getItem(root, "transform"); + if (transform) { + Json *constraintMap; + skeletonData->_transformConstraints.ensureCapacity(transform->_size); + skeletonData->_transformConstraints.setSize(transform->_size, 0); + for (constraintMap = transform->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + + TransformConstraintData *data = new (__FILE__, __LINE__) TransformConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "Transform bone not found: ", boneMap->_valueString); + return NULL; + } + } + + name = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findBone(name); + if (!data->_target) { + delete skeletonData; + setError(root, "Target bone not found: ", name); + return NULL; + } + + data->_local = Json::getInt(constraintMap, "local", 0) ? true : false; + data->_relative = Json::getInt(constraintMap, "relative", 0) ? true : false; + data->_offsetRotation = Json::getFloat(constraintMap, "rotation", 0); + data->_offsetX = Json::getFloat(constraintMap, "x", 0) * _scale; + data->_offsetY = Json::getFloat(constraintMap, "y", 0) * _scale; + data->_offsetScaleX = Json::getFloat(constraintMap, "scaleX", 0); + data->_offsetScaleY = Json::getFloat(constraintMap, "scaleY", 0); + data->_offsetShearY = Json::getFloat(constraintMap, "shearY", 0); + + data->_mixRotate = Json::getFloat(constraintMap, "mixRotate", 1); + data->_mixX = Json::getFloat(constraintMap, "mixX", 1); + data->_mixY = Json::getFloat(constraintMap, "mixY", data->_mixX); + data->_mixScaleX = Json::getFloat(constraintMap, "mixScaleX", 1); + data->_mixScaleY = Json::getFloat(constraintMap, "mixScaleY", data->_mixScaleX); + data->_mixShearY = Json::getFloat(constraintMap, "mixShearY", 1); + + skeletonData->_transformConstraints[i] = data; + } + } + + /* Path constraints */ + path = Json::getItem(root, "path"); + if (path) { + Json *constraintMap; + skeletonData->_pathConstraints.ensureCapacity(path->_size); + skeletonData->_pathConstraints.setSize(path->_size, 0); + for (constraintMap = path->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + const char *item; + + PathConstraintData *data = new (__FILE__, __LINE__) PathConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + boneMap = Json::getItem(constraintMap, "bones"); + data->_bones.ensureCapacity(boneMap->_size); + data->_bones.setSize(boneMap->_size, 0); + for (boneMap = boneMap->_child, ii = 0; boneMap; boneMap = boneMap->_next, ++ii) { + data->_bones[ii] = skeletonData->findBone(boneMap->_valueString); + if (!data->_bones[ii]) { + delete skeletonData; + setError(root, "Path bone not found: ", boneMap->_valueString); + return NULL; + } + } + + name = Json::getString(constraintMap, "target", 0); + data->_target = skeletonData->findSlot(name); + if (!data->_target) { + delete skeletonData; + setError(root, "Target slot not found: ", name); + return NULL; + } + + item = Json::getString(constraintMap, "positionMode", "percent"); + if (strcmp(item, "fixed") == 0) { + data->_positionMode = PositionMode_Fixed; + } else if (strcmp(item, "percent") == 0) { + data->_positionMode = PositionMode_Percent; + } + + item = Json::getString(constraintMap, "spacingMode", "length"); + if (strcmp(item, "length") == 0) data->_spacingMode = SpacingMode_Length; + else if (strcmp(item, "fixed") == 0) + data->_spacingMode = SpacingMode_Fixed; + else if (strcmp(item, "percent") == 0) + data->_spacingMode = SpacingMode_Percent; + else + data->_spacingMode = SpacingMode_Proportional; + + item = Json::getString(constraintMap, "rotateMode", "tangent"); + if (strcmp(item, "tangent") == 0) data->_rotateMode = RotateMode_Tangent; + else if (strcmp(item, "chain") == 0) + data->_rotateMode = RotateMode_Chain; + else if (strcmp(item, "chainScale") == 0) + data->_rotateMode = RotateMode_ChainScale; + + data->_offsetRotation = Json::getFloat(constraintMap, "rotation", 0); + data->_position = Json::getFloat(constraintMap, "position", 0); + if (data->_positionMode == PositionMode_Fixed) data->_position *= _scale; + data->_spacing = Json::getFloat(constraintMap, "spacing", 0); + if (data->_spacingMode == SpacingMode_Length || data->_spacingMode == SpacingMode_Fixed) + data->_spacing *= _scale; + data->_mixRotate = Json::getFloat(constraintMap, "mixRotate", 1); + data->_mixX = Json::getFloat(constraintMap, "mixX", 1); + data->_mixY = Json::getFloat(constraintMap, "mixY", data->_mixX); + + skeletonData->_pathConstraints[i] = data; + } + } + + /* Physics constraints */ + physics = Json::getItem(root, "physics"); + if (physics) { + Json *constraintMap; + skeletonData->_physicsConstraints.ensureCapacity(physics->_size); + skeletonData->_physicsConstraints.setSize(physics->_size, 0); + for (constraintMap = physics->_child, i = 0; constraintMap; constraintMap = constraintMap->_next, ++i) { + const char *name; + + PhysicsConstraintData *data = new (__FILE__, __LINE__) PhysicsConstraintData( + Json::getString(constraintMap, "name", 0)); + data->setOrder(Json::getInt(constraintMap, "order", 0)); + data->setSkinRequired(Json::getBoolean(constraintMap, "skin", false)); + + name = Json::getString(constraintMap, "bone", 0); + data->_bone = skeletonData->findBone(name); + if (!data->_bone) { + delete skeletonData; + setError(root, "Physics bone not found: ", name); + return NULL; + } + + data->_x = Json::getFloat(constraintMap, "x", 0); + data->_y = Json::getFloat(constraintMap, "y", 0); + data->_rotate = Json::getFloat(constraintMap, "rotate", 0); + data->_scaleX = Json::getFloat(constraintMap, "scaleX", 0); + data->_shearX = Json::getFloat(constraintMap, "shearX", 0); + data->_limit = Json::getFloat(constraintMap, "limit", 5000) * _scale; + data->_step = 1.0f / Json::getInt(constraintMap, "fps", 60); + data->_inertia = Json::getFloat(constraintMap, "inertia", 1); + data->_strength = Json::getFloat(constraintMap, "strength", 100); + data->_damping = Json::getFloat(constraintMap, "damping", 1); + data->_massInverse = 1.0f / Json::getFloat(constraintMap, "mass", 1); + data->_wind = Json::getFloat(constraintMap, "wind", 0); + data->_gravity = Json::getFloat(constraintMap, "gravity", 0); + data->_mix = Json::getFloat(constraintMap, "mix", 1); + data->_inertiaGlobal = Json::getBoolean(constraintMap, "inertiaGlobal", false); + data->_strengthGlobal = Json::getBoolean(constraintMap, "strengthGlobal", false); + data->_dampingGlobal = Json::getBoolean(constraintMap, "dampingGlobal", false); + data->_massGlobal = Json::getBoolean(constraintMap, "massGlobal", false); + data->_windGlobal = Json::getBoolean(constraintMap, "windGlobal", false); + data->_gravityGlobal = Json::getBoolean(constraintMap, "gravityGlobal", false); + data->_mixGlobal = Json::getBoolean(constraintMap, "mixGlobal", false); + + skeletonData->_physicsConstraints[i] = data; + } + } + + /* Skins. */ + skins = Json::getItem(root, "skins"); + if (skins) { + Json *skinMap; + skeletonData->_skins.ensureCapacity(skins->_size); + skeletonData->_skins.setSize(skins->_size, 0); + int skinsIndex = 0; + for (skinMap = skins->_child, i = 0; skinMap; skinMap = skinMap->_next, ++i) { + Json *attachmentsMap; + Json *curves; + + Skin *skin = new (__FILE__, __LINE__) Skin(Json::getString(skinMap, "name", "")); + + Json *item = Json::getItem(skinMap, "bones"); + if (item) { + for (item = item->_child; item; item = item->_next) { + BoneData *data = skeletonData->findBone(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin bone not found: "), item->_valueString); + return NULL; + } + skin->getBones().add(data); + } + } + + item = Json::getItem(skinMap, "ik"); + if (item) { + for (item = item->_child; item; item = item->_next) { + IkConstraintData *data = skeletonData->findIkConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin IK constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "transform"); + if (item) { + for (item = item->_child; item; item = item->_next) { + TransformConstraintData *data = skeletonData->findTransformConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin transform constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "path"); + if (item) { + for (item = item->_child; item; item = item->_next) { + PathConstraintData *data = skeletonData->findPathConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin path constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + item = Json::getItem(skinMap, "physics"); + if (item) { + for (item = item->_child; item; item = item->_next) { + PhysicsConstraintData *data = skeletonData->findPhysicsConstraint(item->_valueString); + if (!data) { + delete skeletonData; + setError(root, String("Skin physics constraint not found: "), item->_valueString); + return NULL; + } + skin->getConstraints().add(data); + } + } + + skeletonData->_skins[skinsIndex++] = skin; + if (strcmp(Json::getString(skinMap, "name", ""), "default") == 0) { + skeletonData->_defaultSkin = skin; + } + + Json *attachments = Json::getItem(skinMap, "attachments"); + if (attachments) + for (attachmentsMap = attachments->_child; + attachmentsMap; attachmentsMap = attachmentsMap->_next) { + SlotData *slot = skeletonData->findSlot(attachmentsMap->_name); + Json *attachmentMap; + + for (attachmentMap = attachmentsMap->_child; attachmentMap; attachmentMap = attachmentMap->_next) { + Attachment *attachment = NULL; + const char *skinAttachmentName = attachmentMap->_name; + const char *attachmentName = Json::getString(attachmentMap, "name", skinAttachmentName); + const char *attachmentPath = Json::getString(attachmentMap, "path", attachmentName); + const char *color; + Json *entry; + + const char *typeString = Json::getString(attachmentMap, "type", "region"); + AttachmentType type; + if (strcmp(typeString, "region") == 0) type = AttachmentType_Region; + else if (strcmp(typeString, "mesh") == 0) + type = AttachmentType_Mesh; + else if (strcmp(typeString, "linkedmesh") == 0) + type = AttachmentType_Linkedmesh; + else if (strcmp(typeString, "boundingbox") == 0) + type = AttachmentType_Boundingbox; + else if (strcmp(typeString, "path") == 0) + type = AttachmentType_Path; + else if (strcmp(typeString, "clipping") == 0) + type = AttachmentType_Clipping; + else if (strcmp(typeString, "point") == 0) + type = AttachmentType_Point; + else { + delete skeletonData; + setError(root, "Unknown attachment type: ", typeString); + return NULL; + } + + switch (type) { + case AttachmentType_Region: { + Sequence *sequence = readSequence(Json::getItem(attachmentMap, "sequence")); + attachment = _attachmentLoader->newRegionAttachment(*skin, attachmentName, attachmentPath, sequence); + if (!attachment) { + delete skeletonData; + setError(root, "Error reading attachment: ", skinAttachmentName); + return NULL; + } + + RegionAttachment *region = static_cast(attachment); + region->_path = attachmentPath; + + region->_x = Json::getFloat(attachmentMap, "x", 0) * _scale; + region->_y = Json::getFloat(attachmentMap, "y", 0) * _scale; + region->_scaleX = Json::getFloat(attachmentMap, "scaleX", 1); + region->_scaleY = Json::getFloat(attachmentMap, "scaleY", 1); + region->_rotation = Json::getFloat(attachmentMap, "rotation", 0); + region->_width = Json::getFloat(attachmentMap, "width", 32) * _scale; + region->_height = Json::getFloat(attachmentMap, "height", 32) * _scale; + region->_sequence = sequence; + + color = Json::getString(attachmentMap, "color", 0); + if (color) toColor(region->getColor(), color, true); + + if (region->_region != NULL) region->updateRegion(); + _attachmentLoader->configureAttachment(region); + break; + } + case AttachmentType_Mesh: + case AttachmentType_Linkedmesh: { + Sequence *sequence = readSequence(Json::getItem(attachmentMap, "sequence")); + attachment = _attachmentLoader->newMeshAttachment(*skin, attachmentName, attachmentPath, sequence); + + if (!attachment) { + delete skeletonData; + setError(root, "Error reading attachment: ", skinAttachmentName); + return NULL; + } + + MeshAttachment *mesh = static_cast(attachment); + mesh->_path = attachmentPath; + + color = Json::getString(attachmentMap, "color", 0); + if (color) toColor(mesh->getColor(), color, true); + + mesh->_width = Json::getFloat(attachmentMap, "width", 32) * _scale; + mesh->_height = Json::getFloat(attachmentMap, "height", 32) * _scale; + mesh->_sequence = sequence; + + entry = Json::getItem(attachmentMap, "parent"); + if (!entry) { + int verticesLength; + entry = Json::getItem(attachmentMap, "triangles"); + mesh->_triangles.ensureCapacity(entry->_size); + mesh->_triangles.setSize(entry->_size, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_triangles[ii] = (unsigned short) entry->_valueInt; + + entry = Json::getItem(attachmentMap, "uvs"); + verticesLength = entry->_size; + mesh->_regionUVs.ensureCapacity(verticesLength); + mesh->_regionUVs.setSize(verticesLength, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_regionUVs[ii] = entry->_valueFloat; + + readVertices(attachmentMap, mesh, verticesLength); + + if (mesh->_region != NULL) mesh->updateRegion(); + + mesh->_hullLength = Json::getInt(attachmentMap, "hull", 0); + + entry = Json::getItem(attachmentMap, "edges"); + if (entry) { + mesh->_edges.ensureCapacity(entry->_size); + mesh->_edges.setSize(entry->_size, 0); + for (entry = entry->_child, ii = 0; entry; entry = entry->_next, ++ii) + mesh->_edges[ii] = entry->_valueInt; + } + _attachmentLoader->configureAttachment(mesh); + } else { + bool inheritTimelines = Json::getInt(attachmentMap, "timelines", 1) ? true : false; + LinkedMesh *linkedMesh = new (__FILE__, __LINE__) LinkedMesh(mesh, + String(Json::getString( + attachmentMap, + "skin", 0)), + slot->getIndex(), + String(entry->_valueString), + inheritTimelines); + _linkedMeshes.add(linkedMesh); + } + break; + } + case AttachmentType_Boundingbox: { + attachment = _attachmentLoader->newBoundingBoxAttachment(*skin, attachmentName); + + BoundingBoxAttachment *box = static_cast(attachment); + + int vertexCount = Json::getInt(attachmentMap, "vertexCount", 0) << 1; + readVertices(attachmentMap, box, vertexCount); + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(box->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Path: { + attachment = _attachmentLoader->newPathAttachment(*skin, attachmentName); + + PathAttachment *pathAttatchment = static_cast(attachment); + + int vertexCount = 0; + pathAttatchment->_closed = Json::getInt(attachmentMap, "closed", 0) ? true : false; + pathAttatchment->_constantSpeed = Json::getInt(attachmentMap, "constantSpeed", 1) ? true + : false; + vertexCount = Json::getInt(attachmentMap, "vertexCount", 0); + readVertices(attachmentMap, pathAttatchment, vertexCount << 1); + + pathAttatchment->_lengths.ensureCapacity(vertexCount / 3); + pathAttatchment->_lengths.setSize(vertexCount / 3, 0); + + curves = Json::getItem(attachmentMap, "lengths"); + for (curves = curves->_child, ii = 0; curves; curves = curves->_next, ++ii) + pathAttatchment->_lengths[ii] = curves->_valueFloat * _scale; + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(pathAttatchment->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Point: { + attachment = _attachmentLoader->newPointAttachment(*skin, attachmentName); + + PointAttachment *point = static_cast(attachment); + + point->_x = Json::getFloat(attachmentMap, "x", 0) * _scale; + point->_y = Json::getFloat(attachmentMap, "y", 0) * _scale; + point->_rotation = Json::getFloat(attachmentMap, "rotation", 0); + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(point->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + case AttachmentType_Clipping: { + attachment = _attachmentLoader->newClippingAttachment(*skin, attachmentName); + + ClippingAttachment *clip = static_cast(attachment); + + int vertexCount = 0; + const char *end = Json::getString(attachmentMap, "end", 0); + if (end) clip->_endSlot = skeletonData->findSlot(end); + vertexCount = Json::getInt(attachmentMap, "vertexCount", 0) << 1; + readVertices(attachmentMap, clip, vertexCount); + color = Json::getString(attachmentMap, "color", NULL); + if (color) toColor(clip->getColor(), color, true); + _attachmentLoader->configureAttachment(attachment); + break; + } + } + + skin->setAttachment(slot->getIndex(), skinAttachmentName, attachment); + } + } + } + } + + /* Linked meshes. */ + int n = (int) _linkedMeshes.size(); + for (i = 0; i < n; ++i) { + LinkedMesh *linkedMesh = _linkedMeshes[i]; + Skin *skin = linkedMesh->_skin.length() == 0 ? skeletonData->getDefaultSkin() : skeletonData->findSkin(linkedMesh->_skin); + if (skin == NULL) { + delete skeletonData; + setError(root, "Skin not found: ", linkedMesh->_skin.buffer()); + return NULL; + } + Attachment *parent = skin->getAttachment(linkedMesh->_slotIndex, linkedMesh->_parent); + if (parent == NULL) { + delete skeletonData; + setError(root, "Parent mesh not found: ", linkedMesh->_parent.buffer()); + return NULL; + } + linkedMesh->_mesh->_timelineAttachment = linkedMesh->_inheritTimeline ? static_cast(parent) + : linkedMesh->_mesh; + linkedMesh->_mesh->setParentMesh(static_cast(parent)); + if (linkedMesh->_mesh->_region != NULL) linkedMesh->_mesh->updateRegion(); + _attachmentLoader->configureAttachment(linkedMesh->_mesh); + } + ContainerUtil::cleanUpVectorOfPointers(_linkedMeshes); + _linkedMeshes.clear(); + + /* Events. */ + events = Json::getItem(root, "events"); + if (events) { + Json *eventMap; + skeletonData->_events.ensureCapacity(events->_size); + skeletonData->_events.setSize(events->_size, 0); + for (eventMap = events->_child, i = 0; eventMap; eventMap = eventMap->_next, ++i) { + EventData *eventData = new (__FILE__, __LINE__) EventData(String(eventMap->_name)); + + eventData->_intValue = Json::getInt(eventMap, "int", 0); + eventData->_floatValue = Json::getFloat(eventMap, "float", 0); + const char *stringValue = Json::getString(eventMap, "string", 0); + eventData->_stringValue = stringValue; + const char *audioPath = Json::getString(eventMap, "audio", 0); + eventData->_audioPath = audioPath; + if (audioPath) { + eventData->_volume = Json::getFloat(eventMap, "volume", 1); + eventData->_balance = Json::getFloat(eventMap, "balance", 0); + } + skeletonData->_events[i] = eventData; + } + } + + /* Animations. */ + animations = Json::getItem(root, "animations"); + if (animations) { + Json *animationMap; + skeletonData->_animations.ensureCapacity(animations->_size); + skeletonData->_animations.setSize(animations->_size, 0); + int animationsIndex = 0; + for (animationMap = animations->_child; animationMap; animationMap = animationMap->_next) { + Animation *animation = readAnimation(animationMap, skeletonData); + if (!animation) { + delete skeletonData; + delete root; + return NULL; + } + skeletonData->_animations[animationsIndex++] = animation; + } + } + + delete root; + + return skeletonData; +} + +Sequence *SkeletonJson::readSequence(Json *item) { + if (item == NULL) return NULL; + Sequence *sequence = new Sequence(Json::getInt(item, "count", 0)); + sequence->_start = Json::getInt(item, "start", 1); + sequence->_digits = Json::getInt(item, "digits", 0); + sequence->_setupIndex = Json::getInt(item, "setupIndex", 0); + return sequence; +} + +void SkeletonJson::setBezier(CurveTimeline *timeline, int frame, int value, int bezier, float time1, float value1, float cx1, + float cy1, + float cx2, float cy2, float time2, float value2) { + timeline->setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); +} + +int SkeletonJson::readCurve(Json *curve, CurveTimeline *timeline, int bezier, int frame, int value, float time1, + float time2, + float value1, float value2, float scale) { + if (curve->_type == Json::JSON_STRING && strcmp(curve->_valueString, "stepped") == 0) { + timeline->setStepped(frame); + return bezier; + } + curve = Json::getItem(curve, value << 2); + float cx1 = curve->_valueFloat; + curve = curve->_next; + float cy1 = curve->_valueFloat * scale; + curve = curve->_next; + float cx2 = curve->_valueFloat; + curve = curve->_next; + float cy2 = curve->_valueFloat * scale; + setBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + return bezier + 1; +} + +Timeline *SkeletonJson::readTimeline(Json *keyMap, CurveTimeline1 *timeline, float defaultValue, float scale) { + float time = Json::getFloat(keyMap, "time", 0); + float value = Json::getFloat(keyMap, "value", defaultValue) * scale; + int bezier = 0; + for (int frame = 0;; frame++) { + timeline->setFrame(frame, time, value); + Json *nextMap = keyMap->_next; + if (!nextMap) break; + float time2 = Json::getFloat(nextMap, "time", 0); + float value2 = Json::getFloat(nextMap, "value", defaultValue) * scale; + Json *curve = Json::getItem(keyMap, "curve"); + if (curve != NULL) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); + time = time2; + value = value2; + keyMap = nextMap; + } + // timeline.shrink(); // BOZO + return timeline; +} + +Timeline *SkeletonJson::readTimeline(Json *keyMap, CurveTimeline2 *timeline, const char *name1, const char *name2, + float defaultValue, float scale) { + float time = Json::getFloat(keyMap, "time", 0); + float value1 = Json::getFloat(keyMap, name1, defaultValue) * scale; + float value2 = Json::getFloat(keyMap, name2, defaultValue) * scale; + int bezier = 0; + for (int frame = 0;; frame++) { + timeline->setFrame(frame, time, value1, value2); + Json *nextMap = keyMap->_next; + if (!nextMap) break; + float time2 = Json::getFloat(nextMap, "time", 0); + float nvalue1 = Json::getFloat(nextMap, name1, defaultValue) * scale; + float nvalue2 = Json::getFloat(nextMap, name2, defaultValue) * scale; + Json *curve = Json::getItem(keyMap, "curve"); + if (curve != NULL) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale); + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + keyMap = nextMap; + } + // timeline.shrink(); // BOZO + return timeline; +} + +int SkeletonJson::findSlotIndex(SkeletonData *skeletonData, const String &slotName, Vector timelines) { + int slotIndex = ContainerUtil::findIndexWithName(skeletonData->getSlots(), slotName); + if (slotIndex == -1) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Slot not found: ", slotName); + } + return slotIndex; +} + +Animation *SkeletonJson::readAnimation(Json *root, SkeletonData *skeletonData) { + Vector timelines; + Json *bones = Json::getItem(root, "bones"); + Json *slots = Json::getItem(root, "slots"); + Json *ik = Json::getItem(root, "ik"); + Json *transform = Json::getItem(root, "transform"); + Json *paths = Json::getItem(root, "path"); + Json *physics = Json::getItem(root, "physics"); + Json *attachments = Json::getItem(root, "attachments"); + Json *drawOrder = Json::getItem(root, "drawOrder"); + Json *events = Json::getItem(root, "events"); + Json *boneMap, *slotMap, *keyMap, *nextMap, *curve; + int frame, bezier; + Color color, color2, newColor, newColor2; + + /** Slot timelines. */ + for (slotMap = slots ? slots->_child : 0; slotMap; slotMap = slotMap->_next) { + int slotIndex = findSlotIndex(skeletonData, slotMap->_name, timelines); + if (slotIndex == -1) return NULL; + + for (Json *timelineMap = slotMap->_child; timelineMap; timelineMap = timelineMap->_next) { + int frames = timelineMap->_size; + if (strcmp(timelineMap->_name, "attachment") == 0) { + AttachmentTimeline *timeline = new (__FILE__, __LINE__) AttachmentTimeline(frames, slotIndex); + for (keyMap = timelineMap->_child, frame = 0; keyMap; keyMap = keyMap->_next, ++frame) { + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0), + Json::getItem(keyMap, "name") ? Json::getItem(keyMap, "name")->_valueString : NULL); + } + timelines.add(timeline); + + } else if (strcmp(timelineMap->_name, "rgba") == 0) { + RGBATimeline *timeline = new (__FILE__, __LINE__) RGBATimeline(frames, frames << 2, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "color", 0), true); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b, color.a); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "color", 0), true); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1); + } + time = time2; + color = newColor; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (strcmp(timelineMap->_name, "rgb") == 0) { + RGBTimeline *timeline = new (__FILE__, __LINE__) RGBTimeline(frames, frames * 3, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "color", 0), false); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "color", 0), false); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + } + time = time2; + color = newColor; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (strcmp(timelineMap->_name, "alpha") == 0) { + timelines.add(readTimeline(timelineMap->_child, + new (__FILE__, __LINE__) AlphaTimeline(frames, frames, slotIndex), + 0, 1)); + } else if (strcmp(timelineMap->_name, "rgba2") == 0) { + RGBA2Timeline *timeline = new (__FILE__, __LINE__) RGBA2Timeline(frames, frames * 7, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "light", 0), true); + toColor(color2, Json::getString(keyMap, "dark", 0), false); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b, color.a, color2.r, color2.g, color2.b); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "light", 0), true); + toColor(newColor2, Json::getString(nextMap, "dark", 0), false); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color.a, newColor.a, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.r, newColor2.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.g, newColor2.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, color2.b, newColor2.b, 1); + } + time = time2; + color = newColor; + color2 = newColor2; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (strcmp(timelineMap->_name, "rgb2") == 0) { + RGBA2Timeline *timeline = new (__FILE__, __LINE__) RGBA2Timeline(frames, frames * 6, slotIndex); + keyMap = timelineMap->_child; + float time = Json::getFloat(keyMap, "time", 0); + toColor(color, Json::getString(keyMap, "light", 0), false); + toColor(color2, Json::getString(keyMap, "dark", 0), false); + + for (frame = 0, bezier = 0;; ++frame) { + timeline->setFrame(frame, time, color.r, color.g, color.b, color.a, color2.r, color2.g, color2.b); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + toColor(newColor, Json::getString(nextMap, "light", 0), false); + toColor(newColor2, Json::getString(nextMap, "dark", 0), false); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, color.r, newColor.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, color.g, newColor.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, color.b, newColor.b, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, color2.r, newColor2.r, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, color2.g, newColor2.g, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, color2.b, newColor2.b, 1); + } + time = time2; + color = newColor; + color2 = newColor2; + keyMap = nextMap; + } + timelines.add(timeline); + } else { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Invalid timeline type for a slot: ", timelineMap->_name); + return NULL; + } + } + } + + /** Bone timelines. */ + for (boneMap = bones ? bones->_child : 0; boneMap; boneMap = boneMap->_next) { + int boneIndex = ContainerUtil::findIndexWithName(skeletonData->_bones, boneMap->_name); + if (boneIndex == -1) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Bone not found: ", boneMap->_name); + return NULL; + } + + for (Json *timelineMap = boneMap->_child; timelineMap; timelineMap = timelineMap->_next) { + int frames = timelineMap->_size; + if (frames == 0) continue; + + if (strcmp(timelineMap->_name, "rotate") == 0) { + timelines.add(readTimeline(timelineMap->_child, + new RotateTimeline(frames, frames, boneIndex), 0, + 1)); + } else if (strcmp(timelineMap->_name, "translate") == 0) { + TranslateTimeline *timeline = new TranslateTimeline(frames, frames << 1, + boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, "x", "y", 0, _scale)); + } else if (strcmp(timelineMap->_name, "translatex") == 0) { + TranslateXTimeline *timeline = new TranslateXTimeline(frames, frames, + boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, _scale)); + } else if (strcmp(timelineMap->_name, "translatey") == 0) { + TranslateYTimeline *timeline = new TranslateYTimeline(frames, frames, + boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, _scale)); + } else if (strcmp(timelineMap->_name, "scale") == 0) { + ScaleTimeline *timeline = new (__FILE__, __LINE__) ScaleTimeline(frames, + frames << 1, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, "x", "y", 1, 1)); + } else if (strcmp(timelineMap->_name, "scalex") == 0) { + ScaleXTimeline *timeline = new (__FILE__, __LINE__) ScaleXTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 1, 1)); + } else if (strcmp(timelineMap->_name, "scaley") == 0) { + ScaleYTimeline *timeline = new (__FILE__, __LINE__) ScaleYTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 1, 1)); + } else if (strcmp(timelineMap->_name, "shear") == 0) { + ShearTimeline *timeline = new (__FILE__, __LINE__) ShearTimeline(frames, + frames << 1, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, "x", "y", 0, 1)); + } else if (strcmp(timelineMap->_name, "shearx") == 0) { + ShearXTimeline *timeline = new (__FILE__, __LINE__) ShearXTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, 1)); + } else if (strcmp(timelineMap->_name, "sheary") == 0) { + ShearYTimeline *timeline = new (__FILE__, __LINE__) ShearYTimeline(frames, + frames, boneIndex); + timelines.add(readTimeline(timelineMap->_child, timeline, 0, 1)); + } else if (strcmp(timelineMap->_name, "inherit") == 0) { + InheritTimeline *timeline = new (__FILE__, __LINE__) InheritTimeline(frames, boneIndex); + keyMap = timelineMap->_child; + for (frame = 0;; frame++) { + float time = Json::getFloat(keyMap, "time", 0); + const char *value = Json::getString(keyMap, "inherit", "normal"); + Inherit inherit = Inherit_Normal; + if (strcmp(value, "normal") == 0) inherit = Inherit_Normal; + else if (strcmp(value, "onlyTranslation") == 0) + inherit = Inherit_OnlyTranslation; + else if (strcmp(value, "noRotationOrReflection") == 0) + inherit = Inherit_NoRotationOrReflection; + else if (strcmp(value, "noScale") == 0) + inherit = Inherit_NoScale; + else if (strcmp(value, "noScaleOrReflection") == 0) + inherit = Inherit_NoScaleOrReflection; + timeline->setFrame(frame, time, inherit); + nextMap = keyMap->_next; + if (!nextMap) break; + keyMap = nextMap; + } + timelines.add(timeline); + } else { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Invalid timeline type for a bone: ", timelineMap->_name); + return NULL; + } + } + } + + /** IK constraint timelines. */ + for (Json *constraintMap = ik ? ik->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + keyMap = constraintMap->_child; + if (keyMap == NULL) continue; + + IkConstraintData *constraint = skeletonData->findIkConstraint(constraintMap->_name); + int constraintIndex = skeletonData->_ikConstraints.indexOf(constraint); + IkConstraintTimeline *timeline = new (__FILE__, __LINE__) IkConstraintTimeline(constraintMap->_size, + constraintMap->_size << 1, + constraintIndex); + + float time = Json::getFloat(keyMap, "time", 0); + float mix = Json::getFloat(keyMap, "mix", 1); + float softness = Json::getFloat(keyMap, "softness", 0) * _scale; + + for (frame = 0, bezier = 0;; frame++) { + int bendDirection = Json::getBoolean(keyMap, "bendPositive", true) ? 1 : -1; + timeline->setFrame(frame, time, mix, softness, bendDirection, Json::getBoolean(keyMap, "compress", false), + Json::getBoolean(keyMap, "stretch", false)); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + + float time2 = Json::getFloat(nextMap, "time", 0); + float mix2 = Json::getFloat(nextMap, "mix", 1); + float softness2 = Json::getFloat(nextMap, "softness", 0) * _scale; + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, _scale); + } + + time = time2; + mix = mix2; + softness = softness2; + keyMap = nextMap; + } + + timelines.add(timeline); + } + + /** Transform constraint timelines. */ + for (Json *constraintMap = transform ? transform->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + keyMap = constraintMap->_child; + if (keyMap == NULL) continue; + + TransformConstraintData *constraint = skeletonData->findTransformConstraint(constraintMap->_name); + int constraintIndex = skeletonData->_transformConstraints.indexOf(constraint); + TransformConstraintTimeline *timeline = new (__FILE__, __LINE__) TransformConstraintTimeline( + constraintMap->_size, constraintMap->_size * 6, constraintIndex); + + float time = Json::getFloat(keyMap, "time", 0); + float mixRotate = Json::getFloat(keyMap, "mixRotate", 1); + float mixShearY = Json::getFloat(keyMap, "mixShearY", 1); + float mixX = Json::getFloat(keyMap, "mixX", 1); + float mixY = Json::getFloat(keyMap, "mixY", mixX); + float mixScaleX = Json::getFloat(keyMap, "mixScaleX", 1); + float mixScaleY = Json::getFloat(keyMap, "mixScaleY", mixScaleX); + + for (frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + + float time2 = Json::getFloat(nextMap, "time", 0); + float mixRotate2 = Json::getFloat(nextMap, "mixRotate", 1); + float mixShearY2 = Json::getFloat(nextMap, "mixShearY", 1); + float mixX2 = Json::getFloat(nextMap, "mixX", 1); + float mixY2 = Json::getFloat(nextMap, "mixY", mixX2); + float mixScaleX2 = Json::getFloat(nextMap, "mixScaleX", 1); + float mixScaleY2 = Json::getFloat(nextMap, "mixScaleY", mixScaleX2); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1); + } + + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + mixScaleX = mixScaleX2; + mixScaleY = mixScaleY2; + mixShearY = mixShearY2; + keyMap = nextMap; + } + + timelines.add(timeline); + } + + /** Path constraint timelines. */ + for (Json *constraintMap = paths ? paths->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + PathConstraintData *constraint = skeletonData->findPathConstraint(constraintMap->_name); + if (!constraint) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Path constraint not found: ", constraintMap->_name); + return NULL; + } + int constraintIndex = skeletonData->_pathConstraints.indexOf(constraint); + for (Json *timelineMap = constraintMap->_child; timelineMap; timelineMap = timelineMap->_next) { + keyMap = timelineMap->_child; + if (keyMap == NULL) continue; + const char *timelineName = timelineMap->_name; + int frames = timelineMap->_size; + if (strcmp(timelineName, "position") == 0) { + PathConstraintPositionTimeline *timeline = new (__FILE__, __LINE__) PathConstraintPositionTimeline( + frames, frames, constraintIndex); + timelines.add( + readTimeline(keyMap, timeline, 0, constraint->_positionMode == PositionMode_Fixed ? _scale : 1)); + } else if (strcmp(timelineName, "spacing") == 0) { + CurveTimeline1 *timeline = new PathConstraintSpacingTimeline(frames, frames, + constraintIndex); + timelines.add(readTimeline(keyMap, timeline, 0, + constraint->_spacingMode == SpacingMode_Length || + constraint->_spacingMode == SpacingMode_Fixed + ? _scale + : 1)); + } else if (strcmp(timelineName, "mix") == 0) { + PathConstraintMixTimeline *timeline = new PathConstraintMixTimeline(frames, + frames * 3, constraintIndex); + float time = Json::getFloat(keyMap, "time", 0); + float mixRotate = Json::getFloat(keyMap, "mixRotate", 1); + float mixX = Json::getFloat(keyMap, "mixX", 1); + float mixY = Json::getFloat(keyMap, "mixY", mixX); + for (frame = 0, bezier = 0;; frame++) { + timeline->setFrame(frame, time, mixRotate, mixX, mixY); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + float mixRotate2 = Json::getFloat(nextMap, "mixRotate", 1); + float mixX2 = Json::getFloat(nextMap, "mixX", 1); + float mixY2 = Json::getFloat(nextMap, "mixY", mixX2); + curve = Json::getItem(keyMap, "curve"); + if (curve != NULL) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1); + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + keyMap = nextMap; + } + timelines.add(timeline); + } + } + } + + /** Physics constraint timelines. */ + for (Json *constraintMap = physics ? physics->_child : 0; constraintMap; constraintMap = constraintMap->_next) { + int index = -1; + if (constraintMap->_name && strlen(constraintMap->_name) > 0) { + PhysicsConstraintData *constraint = skeletonData->findPhysicsConstraint(constraintMap->_name); + if (!constraint) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Physics constraint not found: ", constraintMap->_name); + return NULL; + } + index = skeletonData->_physicsConstraints.indexOf(constraint); + } + for (Json *timelineMap = constraintMap->_child; timelineMap; timelineMap = timelineMap->_next) { + keyMap = timelineMap->_child; + if (keyMap == NULL) continue; + const char *timelineName = timelineMap->_name; + int frames = timelineMap->_size; + if (strcmp(timelineName, "reset") == 0) { + PhysicsConstraintResetTimeline *timeline = new (__FILE__, __LINE__) PhysicsConstraintResetTimeline(frames, index); + for (frame = 0; keyMap != nullptr; keyMap = keyMap->_next, frame++) { + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0)); + } + timelines.add(timeline); + continue; + } + + CurveTimeline1 *timeline = nullptr; + if (strcmp(timelineName, "inertia") == 0) { + timeline = new PhysicsConstraintInertiaTimeline(frames, frames, index); + } else if (strcmp(timelineName, "strength") == 0) { + timeline = new PhysicsConstraintStrengthTimeline(frames, frames, index); + } else if (strcmp(timelineName, "damping") == 0) { + timeline = new PhysicsConstraintDampingTimeline(frames, frames, index); + } else if (strcmp(timelineName, "mass") == 0) { + timeline = new PhysicsConstraintMassTimeline(frames, frames, index); + } else if (strcmp(timelineName, "wind") == 0) { + timeline = new PhysicsConstraintWindTimeline(frames, frames, index); + } else if (strcmp(timelineName, "gravity") == 0) { + timeline = new PhysicsConstraintGravityTimeline(frames, frames, index); + } else if (strcmp(timelineName, "mix") == 0) { + timeline = new PhysicsConstraintMixTimeline(frames, frames, index); + } else { + continue; + } + timelines.add(readTimeline(keyMap, timeline, 0, 1)); + } + } + + /** Attachment timelines. */ + for (Json *attachmenstMap = attachments ? attachments->_child : NULL; attachmenstMap; attachmenstMap = attachmenstMap->_next) { + Skin *skin = skeletonData->findSkin(attachmenstMap->_name); + for (slotMap = attachmenstMap->_child; slotMap; slotMap = slotMap->_next) { + int slotIndex = findSlotIndex(skeletonData, slotMap->_name, timelines); + if (slotIndex == -1) return NULL; + + for (Json *attachmentMap = slotMap->_child; attachmentMap; attachmentMap = attachmentMap->_next) { + Attachment *attachment = skin->getAttachment(slotIndex, attachmentMap->_name); + if (!attachment) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Attachment not found: ", attachmentMap->_name); + return NULL; + } + + for (Json *timelineMap = attachmentMap->_child; timelineMap; timelineMap = timelineMap->_next) { + keyMap = timelineMap->_child; + if (keyMap == NULL) continue; + int frames = timelineMap->_size; + String timelineName = timelineMap->_name; + if (timelineName == "deform") { + VertexAttachment *vertexAttachment = static_cast(attachment); + bool weighted = vertexAttachment->_bones.size() != 0; + Vector &verts = vertexAttachment->_vertices; + int deformLength = weighted ? (int) verts.size() / 3 * 2 : (int) verts.size(); + + DeformTimeline *timeline = new (__FILE__, __LINE__) DeformTimeline(frames, + frames, slotIndex, vertexAttachment); + float time = Json::getFloat(keyMap, "time", 0); + for (frame = 0, bezier = 0;; frame++) { + Json *vertices = Json::getItem(keyMap, "vertices"); + Vector deformed; + if (!vertices) { + if (weighted) { + deformed.setSize(deformLength, 0); + } else { + deformed.clearAndAddAll(vertexAttachment->_vertices); + } + } else { + deformed.setSize(deformLength, 0); + int v, start = Json::getInt(keyMap, "offset", 0); + Json *vertex; + if (_scale == 1) { + for (vertex = vertices->_child, v = start; vertex; vertex = vertex->_next, ++v) { + deformed[v] = vertex->_valueFloat; + } + } else { + for (vertex = vertices->_child, v = start; vertex; vertex = vertex->_next, ++v) { + deformed[v] = vertex->_valueFloat * _scale; + } + } + if (!weighted) { + Vector &verticesAttachment = vertexAttachment->_vertices; + for (v = 0; v < deformLength; ++v) { + deformed[v] += verticesAttachment[v]; + } + } + } + timeline->setFrame(frame, time, deformed); + nextMap = keyMap->_next; + if (!nextMap) { + // timeline.shrink(); // BOZO + break; + } + float time2 = Json::getFloat(nextMap, "time", 0); + curve = Json::getItem(keyMap, "curve"); + if (curve) { + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + keyMap = nextMap; + } + timelines.add(timeline); + } else if (timelineName == "sequence") { + SequenceTimeline *timeline = new SequenceTimeline(frames, slotIndex, attachment); + float lastDelay = 0; + for (frame = 0; keyMap != NULL; keyMap = keyMap->_next, frame++) { + float delay = Json::getFloat(keyMap, "delay", lastDelay); + float time = Json::getFloat(keyMap, "time", 0); + String modeString = Json::getString(keyMap, "mode", "hold"); + int index = Json::getInt(keyMap, "index", 0); + SequenceMode mode = SequenceMode::hold; + if (modeString == "once") mode = SequenceMode::once; + if (modeString == "loop") mode = SequenceMode::loop; + if (modeString == "pingpong") mode = SequenceMode::pingpong; + if (modeString == "onceReverse") mode = SequenceMode::onceReverse; + if (modeString == "loopReverse") mode = SequenceMode::loopReverse; + if (modeString == "pingpongReverse") mode = SequenceMode::pingpongReverse; + timeline->setFrame(frame, time, mode, index, delay); + lastDelay = delay; + } + timelines.add(timeline); + } + } + } + } + } + + /** Draw order timeline. */ + if (drawOrder) { + DrawOrderTimeline *timeline = new (__FILE__, __LINE__) DrawOrderTimeline(drawOrder->_size); + + for (keyMap = drawOrder->_child, frame = 0; keyMap; keyMap = keyMap->_next, ++frame) { + int ii; + Vector drawOrder2; + Json *offsets = Json::getItem(keyMap, "offsets"); + if (offsets) { + Json *offsetMap; + Vector unchanged; + unchanged.ensureCapacity(skeletonData->_slots.size() - offsets->_size); + unchanged.setSize(skeletonData->_slots.size() - offsets->_size, 0); + size_t originalIndex = 0, unchangedIndex = 0; + + drawOrder2.ensureCapacity(skeletonData->_slots.size()); + drawOrder2.setSize(skeletonData->_slots.size(), 0); + for (ii = (int) skeletonData->_slots.size() - 1; ii >= 0; --ii) + drawOrder2[ii] = -1; + + for (offsetMap = offsets->_child; offsetMap; offsetMap = offsetMap->_next) { + int slotIndex = findSlotIndex(skeletonData, Json::getString(offsetMap, "slot", 0), timelines); + if (slotIndex == -1) return NULL; + + /* Collect unchanged items. */ + while (originalIndex != (size_t) slotIndex) + unchanged[unchangedIndex++] = (int) originalIndex++; + /* Set changed items. */ + drawOrder2[originalIndex + Json::getInt(offsetMap, "offset", 0)] = (int) originalIndex; + originalIndex++; + } + /* Collect remaining unchanged items. */ + while ((int) originalIndex < (int) skeletonData->_slots.size()) + unchanged[unchangedIndex++] = (int) originalIndex++; + /* Fill in unchanged items. */ + for (ii = (int) skeletonData->_slots.size() - 1; ii >= 0; ii--) + if (drawOrder2[ii] == -1) drawOrder2[ii] = unchanged[--unchangedIndex]; + } + timeline->setFrame(frame, Json::getFloat(keyMap, "time", 0), drawOrder2); + } + timelines.add(timeline); + } + + /** Event timeline. */ + if (events) { + EventTimeline *timeline = new (__FILE__, __LINE__) EventTimeline(events->_size); + + for (keyMap = events->_child, frame = 0; keyMap; keyMap = keyMap->_next, ++frame) { + Event *event; + EventData *eventData = skeletonData->findEvent(Json::getString(keyMap, "name", 0)); + if (!eventData) { + ContainerUtil::cleanUpVectorOfPointers(timelines); + setError(NULL, "Event not found: ", Json::getString(keyMap, "name", 0)); + return NULL; + } + + event = new (__FILE__, __LINE__) Event(Json::getFloat(keyMap, "time", 0), *eventData); + event->_intValue = Json::getInt(keyMap, "int", eventData->_intValue); + event->_floatValue = Json::getFloat(keyMap, "float", eventData->_floatValue); + event->_stringValue = Json::getString(keyMap, "string", eventData->_stringValue.buffer()); + if (!eventData->_audioPath.isEmpty()) { + event->_volume = Json::getFloat(keyMap, "volume", 1); + event->_balance = Json::getFloat(keyMap, "balance", 0); + } + timeline->setFrame(frame, event); + } + timelines.add(timeline); + } + + float duration = 0; + for (size_t i = 0; i < timelines.size(); i++) + duration = MathUtil::max(duration, timelines[i]->getDuration()); + return new (__FILE__, __LINE__) Animation(String(root->_name), timelines, duration); +} + +void SkeletonJson::readVertices(Json *attachmentMap, VertexAttachment *attachment, size_t verticesLength) { + Json *entry; + size_t i, n, nn, entrySize; + Vector vertices; + + attachment->setWorldVerticesLength(verticesLength); + + entry = Json::getItem(attachmentMap, "vertices"); + entrySize = entry->_size; + vertices.ensureCapacity(entrySize); + vertices.setSize(entrySize, 0); + for (entry = entry->_child, i = 0; entry; entry = entry->_next, ++i) + vertices[i] = entry->_valueFloat; + + if (verticesLength == entrySize) { + if (_scale != 1) { + for (i = 0; i < entrySize; ++i) + vertices[i] *= _scale; + } + + attachment->getVertices().clearAndAddAll(vertices); + return; + } + + Vertices bonesAndWeights; + bonesAndWeights._bones.ensureCapacity(verticesLength * 3); + bonesAndWeights._vertices.ensureCapacity(verticesLength * 3 * 3); + + for (i = 0, n = entrySize; i < n;) { + int boneCount = (int) vertices[i++]; + bonesAndWeights._bones.add(boneCount); + for (nn = i + boneCount * 4; i < nn; i += 4) { + bonesAndWeights._bones.add((int) vertices[i]); + bonesAndWeights._vertices.add(vertices[i + 1] * _scale); + bonesAndWeights._vertices.add(vertices[i + 2] * _scale); + bonesAndWeights._vertices.add(vertices[i + 3]); + } + } + + attachment->getVertices().clearAndAddAll(bonesAndWeights._vertices); + attachment->getBones().clearAndAddAll(bonesAndWeights._bones); +} + +void SkeletonJson::setError(Json *root, const String &value1, const String &value2) { + _error = String(value1).append(value2); + delete root; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonRenderer.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonRenderer.cpp new file mode 100644 index 0000000..3617753 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SkeletonRenderer.cpp @@ -0,0 +1,246 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spine; + +SkeletonRenderer::SkeletonRenderer() : _allocator(4096), _worldVertices(), _quadIndices(), _clipping(), _renderCommands() { + _quadIndices.add(0); + _quadIndices.add(1); + _quadIndices.add(2); + _quadIndices.add(2); + _quadIndices.add(3); + _quadIndices.add(0); +} + +SkeletonRenderer::~SkeletonRenderer() { +} + +static RenderCommand *createRenderCommand(BlockAllocator &allocator, int numVertices, int32_t numIndices, BlendMode blendMode, void *texture) { + RenderCommand *cmd = allocator.allocate(1); + cmd->positions = allocator.allocate(numVertices << 1); + cmd->uvs = allocator.allocate(numVertices << 1); + cmd->colors = allocator.allocate(numVertices); + cmd->darkColors = allocator.allocate(numVertices); + cmd->numVertices = numVertices; + cmd->indices = allocator.allocate(numIndices); + cmd->numIndices = numIndices; + cmd->blendMode = blendMode; + cmd->texture = texture; + cmd->next = nullptr; + return cmd; +} + +static RenderCommand *batchSubCommands(BlockAllocator &allocator, Vector &commands, int first, int last, int numVertices, int numIndices) { + RenderCommand *batched = createRenderCommand(allocator, numVertices, numIndices, commands[first]->blendMode, commands[first]->texture); + float *positions = batched->positions; + float *uvs = batched->uvs; + uint32_t *colors = batched->colors; + uint32_t *darkColors = batched->darkColors; + uint16_t *indices = batched->indices; + int indicesOffset = 0; + for (int i = first; i <= last; i++) { + RenderCommand *cmd = commands[i]; + memcpy(positions, cmd->positions, sizeof(float) * 2 * cmd->numVertices); + memcpy(uvs, cmd->uvs, sizeof(float) * 2 * cmd->numVertices); + memcpy(colors, cmd->colors, sizeof(int32_t) * cmd->numVertices); + memcpy(darkColors, cmd->darkColors, sizeof(int32_t) * cmd->numVertices); + for (int ii = 0; ii < cmd->numIndices; ii++) + indices[ii] = cmd->indices[ii] + indicesOffset; + indicesOffset += cmd->numVertices; + positions += 2 * cmd->numVertices; + uvs += 2 * cmd->numVertices; + colors += cmd->numVertices; + darkColors += cmd->numVertices; + indices += cmd->numIndices; + } + return batched; +} + +static RenderCommand *batchCommands(BlockAllocator &allocator, Vector &commands) { + if (commands.size() == 0) return nullptr; + + RenderCommand *root = nullptr; + RenderCommand *last = nullptr; + + RenderCommand *first = commands[0]; + int startIndex = 0; + int i = 1; + int numVertices = first->numVertices; + int numIndices = first->numIndices; + while (i <= (int) commands.size()) { + RenderCommand *cmd = i < (int) commands.size() ? commands[i] : nullptr; + + if (cmd && cmd->numVertices == 0 && cmd->numIndices == 0) { + i++; + continue; + } + + if (cmd != nullptr && cmd->texture == first->texture && + cmd->blendMode == first->blendMode && + cmd->colors[0] == first->colors[0] && + cmd->darkColors[0] == first->darkColors[0] && + numIndices + cmd->numIndices < 0xffff) { + numVertices += cmd->numVertices; + numIndices += cmd->numIndices; + } else { + RenderCommand *batched = batchSubCommands(allocator, commands, startIndex, i - 1, numVertices, numIndices); + if (!last) { + root = last = batched; + } else { + last->next = batched; + last = batched; + } + if (i == (int) commands.size()) break; + first = commands[i]; + startIndex = i; + numVertices = first->numVertices; + numIndices = first->numIndices; + } + i++; + } + return root; +} + +RenderCommand *SkeletonRenderer::render(Skeleton &skeleton) { + _allocator.compress(); + _renderCommands.clear(); + + SkeletonClipping &clipper = _clipping; + + for (unsigned i = 0; i < skeleton.getSlots().size(); ++i) { + Slot &slot = *skeleton.getDrawOrder()[i]; + Attachment *attachment = slot.getAttachment(); + if (!attachment) { + clipper.clipEnd(slot); + continue; + } + + // Early out if the slot color is 0 or the bone is not active + if ((slot.getColor().a == 0 || !slot.getBone().isActive()) && !attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { + clipper.clipEnd(slot); + continue; + } + + Vector *worldVertices = &_worldVertices; + Vector *quadIndices = &_quadIndices; + Vector *vertices = worldVertices; + int32_t verticesCount; + Vector *uvs; + Vector *indices; + int32_t indicesCount; + Color *attachmentColor; + void *texture; + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = (RegionAttachment *) attachment; + attachmentColor = ®ionAttachment->getColor(); + + // Early out if the slot color is 0 + if (attachmentColor->a == 0) { + clipper.clipEnd(slot); + continue; + } + + worldVertices->setSize(8, 0); + regionAttachment->computeWorldVertices(slot, *worldVertices, 0, 2); + verticesCount = 4; + uvs = ®ionAttachment->getUVs(); + indices = quadIndices; + indicesCount = 6; + texture = regionAttachment->getRegion()->rendererObject; + + } else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + MeshAttachment *mesh = (MeshAttachment *) attachment; + attachmentColor = &mesh->getColor(); + + // Early out if the slot color is 0 + if (attachmentColor->a == 0) { + clipper.clipEnd(slot); + continue; + } + + worldVertices->setSize(mesh->getWorldVerticesLength(), 0); + mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), worldVertices->buffer(), 0, 2); + verticesCount = (int32_t) (mesh->getWorldVerticesLength() >> 1); + uvs = &mesh->getUVs(); + indices = &mesh->getTriangles(); + indicesCount = (int32_t) indices->size(); + texture = mesh->getRegion()->rendererObject; + + } else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) { + ClippingAttachment *clip = (ClippingAttachment *) slot.getAttachment(); + clipper.clipStart(slot, clip); + continue; + } else + continue; + + uint8_t r = static_cast(skeleton.getColor().r * slot.getColor().r * attachmentColor->r * 255); + uint8_t g = static_cast(skeleton.getColor().g * slot.getColor().g * attachmentColor->g * 255); + uint8_t b = static_cast(skeleton.getColor().b * slot.getColor().b * attachmentColor->b * 255); + uint8_t a = static_cast(skeleton.getColor().a * slot.getColor().a * attachmentColor->a * 255); + uint32_t color = (a << 24) | (r << 16) | (g << 8) | b; + uint32_t darkColor = 0xff000000; + if (slot.hasDarkColor()) { + Color &slotDarkColor = slot.getDarkColor(); + darkColor = 0xff000000 | (static_cast(slotDarkColor.r * 255) << 16) | (static_cast(slotDarkColor.g * 255) << 8) | static_cast(slotDarkColor.b * 255); + } + + if (clipper.isClipping()) { + clipper.clipTriangles(*worldVertices, *indices, *uvs, 2); + vertices = &clipper.getClippedVertices(); + verticesCount = (int32_t) (clipper.getClippedVertices().size() >> 1); + uvs = &clipper.getClippedUVs(); + indices = &clipper.getClippedTriangles(); + indicesCount = (int32_t) (clipper.getClippedTriangles().size()); + } + + RenderCommand *cmd = createRenderCommand(_allocator, verticesCount, indicesCount, slot.getData().getBlendMode(), texture); + _renderCommands.add(cmd); + memcpy(cmd->positions, vertices->buffer(), (verticesCount << 1) * sizeof(float)); + memcpy(cmd->uvs, uvs->buffer(), (verticesCount << 1) * sizeof(float)); + for (int ii = 0; ii < verticesCount; ii++) { + cmd->colors[ii] = color; + cmd->darkColors[ii] = darkColor; + } + memcpy(cmd->indices, indices->buffer(), indices->size() * sizeof(uint16_t)); + clipper.clipEnd(slot); + } + clipper.clipEnd(); + + return batchCommands(_allocator, _renderCommands); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Skin.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Skin.cpp new file mode 100644 index 0000000..582b26a --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Skin.cpp @@ -0,0 +1,195 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include + +#include + +using namespace spine; + +Skin::AttachmentMap::AttachmentMap() { +} + +static void disposeAttachment(Attachment *attachment) { + if (!attachment) return; + attachment->dereference(); + if (attachment->getRefCount() == 0) delete attachment; +} + +void Skin::AttachmentMap::put(size_t slotIndex, const String &attachmentName, Attachment *attachment) { + if (slotIndex >= _buckets.size()) + _buckets.setSize(slotIndex + 1, Vector()); + Vector &bucket = _buckets[slotIndex]; + int existing = findInBucket(bucket, attachmentName); + attachment->reference(); + if (existing >= 0) { + disposeAttachment(bucket[existing]._attachment); + bucket[existing]._attachment = attachment; + } else { + bucket.add(Entry(slotIndex, attachmentName, attachment)); + } +} + +Attachment *Skin::AttachmentMap::get(size_t slotIndex, const String &attachmentName) { + if (slotIndex >= _buckets.size()) return NULL; + int existing = findInBucket(_buckets[slotIndex], attachmentName); + return existing >= 0 ? _buckets[slotIndex][existing]._attachment : NULL; +} + +void Skin::AttachmentMap::remove(size_t slotIndex, const String &attachmentName) { + if (slotIndex >= _buckets.size()) return; + int existing = findInBucket(_buckets[slotIndex], attachmentName); + if (existing >= 0) { + disposeAttachment(_buckets[slotIndex][existing]._attachment); + _buckets[slotIndex].removeAt(existing); + } +} + +int Skin::AttachmentMap::findInBucket(Vector &bucket, const String &attachmentName) { + for (size_t i = 0; i < bucket.size(); i++) + if (bucket[i]._name == attachmentName) return (int) i; + return -1; +} + +Skin::AttachmentMap::Entries Skin::AttachmentMap::getEntries() { + return Skin::AttachmentMap::Entries(_buckets); +} + +Skin::Skin(const String &name) : _name(name), _attachments(), _color(0.99607843f, 0.61960787f, 0.30980393f, 1) { + assert(_name.length() > 0); +} + +Skin::~Skin() { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry entry = entries.next(); + disposeAttachment(entry._attachment); + } +} + +void Skin::setAttachment(size_t slotIndex, const String &name, Attachment *attachment) { + assert(attachment); + _attachments.put(slotIndex, name, attachment); +} + +Attachment *Skin::getAttachment(size_t slotIndex, const String &name) { + return _attachments.get(slotIndex, name); +} + +void Skin::removeAttachment(size_t slotIndex, const String &name) { + _attachments.remove(slotIndex, name); +} + +void Skin::findNamesForSlot(size_t slotIndex, Vector &names) { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == slotIndex) { + names.add(entry._name); + } + } +} + +void Skin::findAttachmentsForSlot(size_t slotIndex, Vector &attachments) { + Skin::AttachmentMap::Entries entries = _attachments.getEntries(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + if (entry._slotIndex == slotIndex) attachments.add(entry._attachment); + } +} + +const String &Skin::getName() { + return _name; +} + +Skin::AttachmentMap::Entries Skin::getAttachments() { + return _attachments.getEntries(); +} + +void Skin::attachAll(Skeleton &skeleton, Skin &oldSkin) { + Vector &slots = skeleton.getSlots(); + Skin::AttachmentMap::Entries entries = oldSkin.getAttachments(); + while (entries.hasNext()) { + Skin::AttachmentMap::Entry &entry = entries.next(); + int slotIndex = (int) entry._slotIndex; + Slot *slot = slots[slotIndex]; + + if (slot->getAttachment() == entry._attachment) { + Attachment *attachment = getAttachment(slotIndex, entry._name); + if (attachment) slot->setAttachment(attachment); + } + } +} + +void Skin::addSkin(Skin *other) { + for (size_t i = 0; i < other->getBones().size(); i++) + if (!_bones.contains(other->getBones()[i])) _bones.add(other->getBones()[i]); + + for (size_t i = 0; i < other->getConstraints().size(); i++) + if (!_constraints.contains(other->getConstraints()[i])) _constraints.add(other->getConstraints()[i]); + + AttachmentMap::Entries entries = other->getAttachments(); + while (entries.hasNext()) { + AttachmentMap::Entry &entry = entries.next(); + setAttachment(entry._slotIndex, entry._name, entry._attachment); + } +} + +void Skin::copySkin(Skin *other) { + for (size_t i = 0; i < other->getBones().size(); i++) + if (!_bones.contains(other->getBones()[i])) _bones.add(other->getBones()[i]); + + for (size_t i = 0; i < other->getConstraints().size(); i++) + if (!_constraints.contains(other->getConstraints()[i])) _constraints.add(other->getConstraints()[i]); + + AttachmentMap::Entries entries = other->getAttachments(); + while (entries.hasNext()) { + AttachmentMap::Entry &entry = entries.next(); + if (entry._attachment->getRTTI().isExactly(MeshAttachment::rtti)) + setAttachment(entry._slotIndex, entry._name, + static_cast(entry._attachment)->newLinkedMesh()); + else + setAttachment(entry._slotIndex, entry._name, entry._attachment->copy()); + } +} + +Vector &Skin::getConstraints() { + return _constraints; +} + +Vector &Skin::getBones() { + return _bones; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Slot.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Slot.cpp new file mode 100644 index 0000000..a0079f0 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Slot.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +using namespace spine; + +Slot::Slot(SlotData &data, Bone &bone) : _data(data), + _bone(bone), + _skeleton(bone.getSkeleton()), + _color(1, 1, 1, 1), + _darkColor(0, 0, 0, 0), + _hasDarkColor(data.hasDarkColor()), + _attachment(NULL), + _attachmentState(0), + _sequenceIndex(0) { + setToSetupPose(); +} + +void Slot::setToSetupPose() { + _color.set(_data.getColor()); + if (_hasDarkColor) _darkColor.set(_data.getDarkColor()); + + const String &attachmentName = _data.getAttachmentName(); + if (attachmentName.length() > 0) { + _attachment = NULL; + setAttachment(_skeleton.getAttachment(_data.getIndex(), attachmentName)); + } else { + setAttachment(NULL); + } +} + +SlotData &Slot::getData() { + return _data; +} + +Bone &Slot::getBone() { + return _bone; +} + +Skeleton &Slot::getSkeleton() { + return _skeleton; +} + +Color &Slot::getColor() { + return _color; +} + +Color &Slot::getDarkColor() { + return _darkColor; +} + +bool Slot::hasDarkColor() { + return _hasDarkColor; +} + +Attachment *Slot::getAttachment() { + return _attachment; +} + +void Slot::setAttachment(Attachment *inValue) { + if (_attachment == inValue) { + return; + } + + if (!inValue || + !_attachment || + !inValue->getRTTI().instanceOf(VertexAttachment::rtti) || + !_attachment->getRTTI().instanceOf(VertexAttachment::rtti) || + static_cast(inValue)->getTimelineAttachment() != + static_cast(_attachment)->getTimelineAttachment()) { + _deform.clear(); + } + + _attachment = inValue; + _sequenceIndex = -1; +} + +int Slot::getAttachmentState() { + return _attachmentState; +} + +void Slot::setAttachmentState(int state) { + _attachmentState = state; +} + +Vector &Slot::getDeform() { + return _deform; +} + +int Slot::getSequenceIndex() { + return _sequenceIndex; +} + +void Slot::setSequenceIndex(int index) { + _sequenceIndex = index; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SlotData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SlotData.cpp new file mode 100644 index 0000000..bc9694a --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SlotData.cpp @@ -0,0 +1,99 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +SlotData::SlotData(int index, const String &name, BoneData &boneData) : _index(index), + _name(name), + _boneData(boneData), + _color(1, 1, 1, 1), + _darkColor(0, 0, 0, 0), + _hasDarkColor(false), + _attachmentName(), + _blendMode(BlendMode_Normal), + _visible(true) { + assert(_index >= 0); + assert(_name.length() > 0); +} + +int SlotData::getIndex() { + return _index; +} + +const String &SlotData::getName() { + return _name; +} + +BoneData &SlotData::getBoneData() { + return _boneData; +} + +Color &SlotData::getColor() { + return _color; +} + +Color &SlotData::getDarkColor() { + return _darkColor; +} + +bool SlotData::hasDarkColor() { + return _hasDarkColor; +} + +void SlotData::setHasDarkColor(bool inValue) { + _hasDarkColor = inValue; +} + +const String &SlotData::getAttachmentName() { + return _attachmentName; +} + +void SlotData::setAttachmentName(const String &inValue) { + _attachmentName = inValue; +} + +BlendMode SlotData::getBlendMode() { + return _blendMode; +} + +void SlotData::setBlendMode(BlendMode inValue) { + _blendMode = inValue; +} + +bool SlotData::isVisible() { + return _visible; +} + +void SlotData::setVisible(bool inValue) { + this->_visible = inValue; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SpineObject.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SpineObject.cpp new file mode 100644 index 0000000..d71bf6e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/SpineObject.cpp @@ -0,0 +1,63 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include + +using namespace spine; + +void *SpineObject::operator new(size_t sz) { + return SpineExtension::getInstance()->_calloc(sz, __FILE__, __LINE__); +} + +void *SpineObject::operator new(size_t sz, const char *file, int line) { + return SpineExtension::getInstance()->_calloc(sz, file, line); +} + +void *SpineObject::operator new(size_t sz, void *ptr) { + SP_UNUSED(sz); + return ptr; +} + +void SpineObject::operator delete(void *p, const char *file, int line) { + SpineExtension::free(p, file, line); +} + +void SpineObject::operator delete(void *p, void *mem) { + SP_UNUSED(mem); + SpineExtension::free(p, __FILE__, __LINE__); +} + +void SpineObject::operator delete(void *p) { + SpineExtension::free(p, __FILE__, __LINE__); +} + +SpineObject::~SpineObject() { + SpineExtension::beforeFree(this); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TextureLoader.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TextureLoader.cpp new file mode 100644 index 0000000..4dd2a5f --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TextureLoader.cpp @@ -0,0 +1,38 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +namespace spine { + TextureLoader::TextureLoader() { + } + + TextureLoader::~TextureLoader() { + } +}// namespace spine diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Timeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Timeline.cpp new file mode 100644 index 0000000..b8624c0 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Timeline.cpp @@ -0,0 +1,73 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +namespace spine { + RTTI_IMPL_NOPARENT(Timeline) + + Timeline::Timeline(size_t frameCount, size_t frameEntries) + : _propertyIds(), _frames(), _frameEntries(frameEntries) { + _frames.setSize(frameCount * frameEntries, 0); + } + + Timeline::~Timeline() { + } + + Vector &Timeline::getPropertyIds() { + return _propertyIds; + } + + void Timeline::setPropertyIds(PropertyId propertyIds[], size_t propertyIdsCount) { + _propertyIds.clear(); + _propertyIds.ensureCapacity(propertyIdsCount); + for (size_t i = 0; i < propertyIdsCount; i++) { + _propertyIds.add(propertyIds[i]); + } + } + + size_t Timeline::getFrameCount() { + return _frames.size() / _frameEntries; + } + + Vector &Timeline::getFrames() { + return _frames; + } + + size_t Timeline::getFrameEntries() { + return _frameEntries; + } + + float Timeline::getDuration() { + return _frames[_frames.size() - getFrameEntries()]; + } +}// namespace spine diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TransformConstraint.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TransformConstraint.cpp new file mode 100644 index 0000000..3fea0ac --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TransformConstraint.cpp @@ -0,0 +1,350 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include +#include + +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraint, Updatable) + +TransformConstraint::TransformConstraint(TransformConstraintData &data, Skeleton &skeleton) : Updatable(), + _data(data), + _target(skeleton.findBone( + data.getTarget()->getName())), + _mixRotate( + data.getMixRotate()), + _mixX(data.getMixX()), + _mixY(data.getMixY()), + _mixScaleX( + data.getMixScaleX()), + _mixScaleY( + data.getMixScaleY()), + _mixShearY( + data.getMixShearY()), + _active(false) { + _bones.ensureCapacity(_data.getBones().size()); + for (size_t i = 0; i < _data.getBones().size(); ++i) { + BoneData *boneData = _data.getBones()[i]; + _bones.add(skeleton.findBone(boneData->getName())); + } +} + +void TransformConstraint::update(Physics) { + if (_mixRotate == 0 && _mixX == 0 && _mixY == 0 && _mixScaleX == 0 && _mixScaleY == 0 && _mixShearY == 0) return; + + if (_data.isLocal()) { + if (_data.isRelative()) + applyRelativeLocal(); + else + applyAbsoluteLocal(); + } else { + if (_data.isRelative()) + applyRelativeWorld(); + else + applyAbsoluteWorld(); + } +} + +int TransformConstraint::getOrder() { + return (int) _data.getOrder(); +} + +TransformConstraintData &TransformConstraint::getData() { + return _data; +} + +Vector &TransformConstraint::getBones() { + return _bones; +} + +Bone *TransformConstraint::getTarget() { + return _target; +} + +void TransformConstraint::setTarget(Bone *inValue) { + _target = inValue; +} + +float TransformConstraint::getMixRotate() { + return _mixRotate; +} + +void TransformConstraint::setMixRotate(float inValue) { + _mixRotate = inValue; +} + +float TransformConstraint::getMixX() { + return _mixX; +} + +void TransformConstraint::setMixX(float inValue) { + _mixX = inValue; +} + +float TransformConstraint::getMixY() { + return _mixY; +} + +void TransformConstraint::setMixY(float inValue) { + _mixY = inValue; +} + +void TransformConstraint::setMixScaleX(float inValue) { + _mixScaleX = inValue; +} + +float TransformConstraint::getMixScaleX() { + return _mixScaleX; +} + +float TransformConstraint::getMixScaleY() { + return _mixScaleY; +} + +void TransformConstraint::setMixScaleY(float inValue) { + _mixScaleY = inValue; +} + +float TransformConstraint::getMixShearY() { + return _mixShearY; +} + +void TransformConstraint::setMixShearY(float inValue) { + _mixShearY = inValue; +} + +void TransformConstraint::applyAbsoluteWorld() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + bool translate = mixX != 0 || mixY != 0; + Bone &target = *_target; + float ta = target._a, tb = target._b, tc = target._c, td = target._d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + float offsetRotation = _data._offsetRotation * degRadReflect, offsetShearY = _data._offsetShearY * degRadReflect; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + if (mixRotate != 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + float r = MathUtil::atan2(tc, ta) - MathUtil::atan2(c, a) + offsetRotation; + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= mixRotate; + float cos = MathUtil::cos(r), sin = MathUtil::sin(r); + bone._a = cos * a - sin * c; + bone._b = cos * b - sin * d; + bone._c = sin * a + cos * c; + bone._d = sin * b + cos * d; + } + + if (translate) { + float tx, ty; + target.localToWorld(_data._offsetX, _data._offsetY, tx, ty); + bone._worldX += (tx - bone._worldX) * mixX; + bone._worldY += (ty - bone._worldY) * mixY; + } + + if (mixScaleX > 0) { + float s = MathUtil::sqrt(bone._a * bone._a + bone._c * bone._c); + if (s != 0) s = (s + (MathUtil::sqrt(ta * ta + tc * tc) - s + _data._offsetScaleX) * mixScaleX) / s; + bone._a *= s; + bone._c *= s; + } + + if (mixScaleY > 0) { + float s = MathUtil::sqrt(bone._b * bone._b + bone._d * bone._d); + if (s != 0) s = (s + (MathUtil::sqrt(tb * tb + td * td) - s + _data._offsetScaleY) * mixScaleY) / s; + bone._b *= s; + bone._d *= s; + } + + if (mixShearY > 0) { + float b = bone._b, d = bone._d; + float by = MathUtil::atan2(d, b); + float r = MathUtil::atan2(td, tb) - MathUtil::atan2(tc, ta) - (by - MathUtil::atan2(bone._c, bone._a)); + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r = by + (r + offsetShearY) * mixShearY; + float s = MathUtil::sqrt(b * b + d * d); + bone._b = MathUtil::cos(r) * s; + bone._d = MathUtil::sin(r) * s; + } + + bone.updateAppliedTransform(); + } +} + +void TransformConstraint::applyRelativeWorld() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + bool translate = mixX != 0 || mixY != 0; + Bone &target = *_target; + float ta = target._a, tb = target._b, tc = target._c, td = target._d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtil::Deg_Rad : -MathUtil::Deg_Rad; + float offsetRotation = _data._offsetRotation * degRadReflect, offsetShearY = _data._offsetShearY * degRadReflect; + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + if (mixRotate != 0) { + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + float r = MathUtil::atan2(tc, ta) + offsetRotation; + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + r *= mixRotate; + float cos = MathUtil::cos(r), sin = MathUtil::sin(r); + bone._a = cos * a - sin * c; + bone._b = cos * b - sin * d; + bone._c = sin * a + cos * c; + bone._d = sin * b + cos * d; + } + + if (translate) { + float tx, ty; + target.localToWorld(_data._offsetX, _data._offsetY, tx, ty); + bone._worldX += tx * mixX; + bone._worldY += ty * mixY; + } + + if (mixScaleX != 0) { + float s = (MathUtil::sqrt(ta * ta + tc * tc) - 1 + _data._offsetScaleX) * mixScaleX + 1; + bone._a *= s; + bone._c *= s; + } + if (mixScaleY != 0) { + float s = (MathUtil::sqrt(tb * tb + td * td) - 1 + _data._offsetScaleY) * mixScaleY + 1; + bone._b *= s; + bone._d *= s; + } + + if (mixShearY > 0) { + float r = MathUtil::atan2(td, tb) - MathUtil::atan2(tc, ta); + if (r > MathUtil::Pi) + r -= MathUtil::Pi_2; + else if (r < -MathUtil::Pi) + r += MathUtil::Pi_2; + + float b = bone._b, d = bone._d; + r = MathUtil::atan2(d, b) + (r - MathUtil::Pi / 2 + offsetShearY) * mixShearY; + float s = MathUtil::sqrt(b * b + d * d); + bone._b = MathUtil::cos(r) * s; + bone._d = MathUtil::sin(r) * s; + } + + bone.updateAppliedTransform(); + } +} + +void TransformConstraint::applyAbsoluteLocal() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + Bone &target = *_target; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + float rotation = bone._arotation; + if (mixRotate != 0) { + float r = target._arotation - rotation + _data._offsetRotation; + r -= MathUtil::ceil(r / 360 - 0.5) * 360; + rotation += r * mixRotate; + } + + float x = bone._ax, y = bone._ay; + x += (target._ax - x + _data._offsetX) * mixX; + y += (target._ay - y + _data._offsetY) * mixY; + + float scaleX = bone._ascaleX, scaleY = bone._ascaleY; + if (mixScaleX != 0 && scaleX != 0) + scaleX = (scaleX + (target._ascaleX - scaleX + _data._offsetScaleX) * mixScaleX) / scaleX; + if (mixScaleY != 0 && scaleY != 0) + scaleY = (scaleY + (target._ascaleY - scaleY + _data._offsetScaleY) * mixScaleY) / scaleY; + + float shearY = bone._ashearY; + if (mixShearY != 0) { + float r = target._ashearY - shearY + _data._offsetShearY; + r -= MathUtil::ceil(r / 360 - 0.5) * 360; + bone._shearY += r * mixShearY; + } + + bone.updateWorldTransform(x, y, rotation, scaleX, scaleY, bone._ashearX, shearY); + } +} + +void TransformConstraint::applyRelativeLocal() { + float mixRotate = _mixRotate, mixX = _mixX, mixY = _mixY, mixScaleX = _mixScaleX, mixScaleY = _mixScaleY, mixShearY = _mixShearY; + Bone &target = *_target; + + for (size_t i = 0; i < _bones.size(); ++i) { + Bone *item = _bones[i]; + Bone &bone = *item; + + float rotation = bone._arotation + (target._arotation + _data._offsetRotation) * mixRotate; + float x = bone._ax + (target._ax + _data._offsetX) * mixX; + float y = bone._ay + (target._ay + _data._offsetY) * mixY; + float scaleX = bone._ascaleX * (((target._ascaleX - 1 + _data._offsetScaleX) * mixScaleX) + 1); + float scaleY = bone._ascaleY * (((target._ascaleY - 1 + _data._offsetScaleY) * mixScaleY) + 1); + float shearY = bone._ashearY + (target._ashearY + _data._offsetShearY) * mixShearY; + + bone.updateWorldTransform(x, y, rotation, scaleX, scaleY, bone._ashearX, shearY); + } +} + +bool TransformConstraint::isActive() { + return _active; +} + +void TransformConstraint::setActive(bool inValue) { + _active = inValue; +} + +void TransformConstraint::setToSetupPose() { + TransformConstraintData &data = this->_data; + this->_mixRotate = data._mixRotate; + this->_mixX = data._mixX; + this->_mixY = data._mixY; + this->_mixScaleX = data._mixScaleX; + this->_mixScaleY = data._mixScaleY; + this->_mixShearY = data._mixShearY; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TransformConstraintData.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TransformConstraintData.cpp new file mode 100644 index 0000000..38a674e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TransformConstraintData.cpp @@ -0,0 +1,180 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraintData, ConstraintData) + +TransformConstraintData::TransformConstraintData(const String &name) : ConstraintData(name), + _target(NULL), + _mixRotate(0), + _mixX(0), + _mixY(0), + _mixScaleX(0), + _mixScaleY(0), + _mixShearY(0), + _offsetRotation(0), + _offsetX(0), + _offsetY(0), + _offsetScaleX(0), + _offsetScaleY(0), + _offsetShearY(0), + _relative(false), + _local(false) { +} + +Vector &TransformConstraintData::getBones() { + return _bones; +} + +BoneData *TransformConstraintData::getTarget() { + return _target; +} + +float TransformConstraintData::getMixRotate() { + return _mixRotate; +} + +float TransformConstraintData::getMixX() { + return _mixX; +} + +float TransformConstraintData::getMixY() { + return _mixY; +} + +float TransformConstraintData::getMixScaleX() { + return _mixScaleX; +} + +float TransformConstraintData::getMixScaleY() { + return _mixScaleY; +} + +float TransformConstraintData::getMixShearY() { + return _mixShearY; +} + +float TransformConstraintData::getOffsetRotation() { + return _offsetRotation; +} + +float TransformConstraintData::getOffsetX() { + return _offsetX; +} + +float TransformConstraintData::getOffsetY() { + return _offsetY; +} + +float TransformConstraintData::getOffsetScaleX() { + return _offsetScaleX; +} + +float TransformConstraintData::getOffsetScaleY() { + return _offsetScaleY; +} + +float TransformConstraintData::getOffsetShearY() { + return _offsetShearY; +} + +bool TransformConstraintData::isRelative() { + return _relative; +} + +bool TransformConstraintData::isLocal() { + return _local; +} + +void TransformConstraintData::setTarget(BoneData *target) { + _target = target; +} + +void TransformConstraintData::setMixRotate(float mixRotate) { + _mixRotate = mixRotate; +} + +void TransformConstraintData::setMixX(float mixX) { + _mixX = mixX; +} + +void TransformConstraintData::setMixY(float mixY) { + _mixY = mixY; +} + +void TransformConstraintData::setMixScaleX(float mixScaleX) { + _mixScaleX = mixScaleX; +} + +void TransformConstraintData::setMixScaleY(float mixScaleY) { + _mixScaleY = mixScaleY; +} + +void TransformConstraintData::setMixShearY(float mixShearY) { + _mixShearY = mixShearY; +} + +void TransformConstraintData::setOffsetRotation(float offsetRotation) { + _offsetRotation = offsetRotation; +} + +void TransformConstraintData::setOffsetX(float offsetX) { + _offsetX = offsetX; +} + +void TransformConstraintData::setOffsetY(float offsetY) { + _offsetY = offsetY; +} + +void TransformConstraintData::setOffsetScaleX(float offsetScaleX) { + _offsetScaleX = offsetScaleX; +} + +void TransformConstraintData::setOffsetScaleY(float offsetScaleY) { + _offsetScaleY = offsetScaleY; +} + +void TransformConstraintData::setOffsetShearY(float offsetShearY) { + _offsetShearY = offsetShearY; +} + +void TransformConstraintData::setRelative(bool isRelative) { + _relative = isRelative; +} + +void TransformConstraintData::setLocal(bool isLocal) { + _local = isLocal; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TransformConstraintTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TransformConstraintTimeline.cpp new file mode 100644 index 0000000..6e41e2e --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TransformConstraintTimeline.cpp @@ -0,0 +1,157 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(TransformConstraintTimeline, CurveTimeline) + +TransformConstraintTimeline::TransformConstraintTimeline(size_t frameCount, size_t bezierCount, + int transformConstraintIndex) : CurveTimeline(frameCount, + TransformConstraintTimeline::ENTRIES, + bezierCount), + _constraintIndex( + transformConstraintIndex) { + PropertyId ids[] = {((PropertyId) Property_TransformConstraint << 32) | transformConstraintIndex}; + setPropertyIds(ids, 1); +} + +void TransformConstraintTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, + float alpha, MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + TransformConstraint *constraintP = skeleton._transformConstraints[_constraintIndex]; + TransformConstraint &constraint = *constraintP; + if (!constraint.isActive()) return; + + TransformConstraintData &data = constraint._data; + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + constraint._mixRotate = data._mixRotate; + constraint._mixX = data._mixX; + constraint._mixY = data._mixY; + constraint._mixScaleX = data._mixScaleX; + constraint._mixScaleY = data._mixScaleY; + constraint._mixShearY = data._mixShearY; + return; + case MixBlend_First: + constraint._mixRotate += (data._mixRotate - constraint._mixRotate) * alpha; + constraint._mixX += (data._mixX - constraint._mixX) * alpha; + constraint._mixY += (data._mixY - constraint._mixY) * alpha; + constraint._mixScaleX += (data._mixScaleX - constraint._mixScaleX) * alpha; + constraint._mixScaleY += (data._mixScaleY - constraint._mixScaleY) * alpha; + constraint._mixShearY += (data._mixShearY - constraint._mixShearY) * alpha; + return; + default: + return; + } + } + + float rotate, x, y, scaleX, scaleY, shearY; + int i = Animation::search(_frames, time, TransformConstraintTimeline::ENTRIES); + int curveType = (int) _curves[i / TransformConstraintTimeline::ENTRIES]; + switch (curveType) { + case TransformConstraintTimeline::LINEAR: { + float before = _frames[i]; + rotate = _frames[i + ROTATE]; + x = _frames[i + X]; + y = _frames[i + Y]; + scaleX = _frames[i + SCALEX]; + scaleY = _frames[i + SCALEY]; + shearY = _frames[i + SHEARY]; + float t = (time - before) / (_frames[i + ENTRIES] - before); + rotate += (_frames[i + ENTRIES + ROTATE] - rotate) * t; + x += (_frames[i + ENTRIES + X] - x) * t; + y += (_frames[i + ENTRIES + Y] - y) * t; + scaleX += (_frames[i + ENTRIES + SCALEX] - scaleX) * t; + scaleY += (_frames[i + ENTRIES + SCALEY] - scaleY) * t; + shearY += (_frames[i + ENTRIES + SHEARY] - shearY) * t; + break; + } + case TransformConstraintTimeline::STEPPED: { + rotate = _frames[i + ROTATE]; + x = _frames[i + X]; + y = _frames[i + Y]; + scaleX = _frames[i + SCALEX]; + scaleY = _frames[i + SCALEY]; + shearY = _frames[i + SHEARY]; + break; + } + default: { + rotate = getBezierValue(time, i, ROTATE, curveType - BEZIER); + x = getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER); + y = getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER); + scaleX = getBezierValue(time, i, SCALEX, curveType + BEZIER_SIZE * 3 - BEZIER); + scaleY = getBezierValue(time, i, SCALEY, curveType + BEZIER_SIZE * 4 - BEZIER); + shearY = getBezierValue(time, i, SHEARY, curveType + BEZIER_SIZE * 5 - BEZIER); + } + } + + if (blend == MixBlend_Setup) { + constraint._mixRotate = data._mixRotate + (rotate - data._mixRotate) * alpha; + constraint._mixX = data._mixX + (x - data._mixX) * alpha; + constraint._mixY = data._mixY + (y - data._mixY) * alpha; + constraint._mixScaleX = data._mixScaleX + (scaleX - data._mixScaleX) * alpha; + constraint._mixScaleY = data._mixScaleY + (scaleY - data._mixScaleY) * alpha; + constraint._mixShearY = data._mixShearY + (shearY - data._mixShearY) * alpha; + } else { + constraint._mixRotate += (rotate - constraint._mixRotate) * alpha; + constraint._mixX += (x - constraint._mixX) * alpha; + constraint._mixY += (y - constraint._mixY) * alpha; + constraint._mixScaleX += (scaleX - constraint._mixScaleX) * alpha; + constraint._mixScaleY += (scaleY - constraint._mixScaleY) * alpha; + constraint._mixShearY += (shearY - constraint._mixShearY) * alpha; + } +} + +void TransformConstraintTimeline::setFrame(size_t frame, float time, float mixRotate, float mixX, float mixY, + float mixScaleX, float mixScaleY, float mixShearY) { + frame *= ENTRIES; + _frames[frame] = time; + _frames[frame + ROTATE] = mixRotate; + _frames[frame + X] = mixX; + _frames[frame + Y] = mixY; + _frames[frame + SCALEX] = mixScaleX; + _frames[frame + SCALEY] = mixScaleY; + _frames[frame + SHEARY] = mixShearY; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TranslateTimeline.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TranslateTimeline.cpp new file mode 100644 index 0000000..1299795 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/TranslateTimeline.cpp @@ -0,0 +1,162 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace spine; + +RTTI_IMPL(TranslateTimeline, CurveTimeline2) + +TranslateTimeline::TranslateTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline2(frameCount, + bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_X << 32) | boneIndex, + ((PropertyId) Property_Y << 32) | boneIndex}; + setPropertyIds(ids, 2); +} + +TranslateTimeline::~TranslateTimeline() { +} + +void TranslateTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (!bone->_active) return; + + if (time < _frames[0]) { + switch (blend) { + case MixBlend_Setup: + bone->_x = bone->_data._x; + bone->_y = bone->_data._y; + return; + case MixBlend_First: + bone->_x += (bone->_data._x - bone->_x) * alpha; + bone->_y += (bone->_data._y - bone->_y) * alpha; + default: { + } + } + return; + } + + float x = 0, y = 0; + int i = Animation::search(_frames, time, CurveTimeline2::ENTRIES); + int curveType = (int) _curves[i / CurveTimeline2::ENTRIES]; + switch (curveType) { + case CurveTimeline::LINEAR: { + float before = _frames[i]; + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + float t = (time - before) / (_frames[i + CurveTimeline2::ENTRIES] - before); + x += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE1] - x) * t; + y += (_frames[i + CurveTimeline2::ENTRIES + CurveTimeline2::VALUE2] - y) * t; + break; + } + case CurveTimeline::STEPPED: { + x = _frames[i + CurveTimeline2::VALUE1]; + y = _frames[i + CurveTimeline2::VALUE2]; + break; + } + default: { + x = getBezierValue(time, i, CurveTimeline2::VALUE1, curveType - CurveTimeline::BEZIER); + y = getBezierValue(time, i, CurveTimeline2::VALUE2, + curveType + CurveTimeline::BEZIER_SIZE - CurveTimeline::BEZIER); + } + } + + switch (blend) { + case MixBlend_Setup: + bone->_x = bone->_data._x + x * alpha; + bone->_y = bone->_data._y + y * alpha; + break; + case MixBlend_First: + case MixBlend_Replace: + bone->_x += (bone->_data._x + x - bone->_x) * alpha; + bone->_y += (bone->_data._y + y - bone->_y) * alpha; + break; + case MixBlend_Add: + bone->_x += x * alpha; + bone->_y += y * alpha; + } +} + +RTTI_IMPL(TranslateXTimeline, CurveTimeline1) + +TranslateXTimeline::TranslateXTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1( + frameCount, bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_X << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +TranslateXTimeline::~TranslateXTimeline() { +} + +void TranslateXTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_x = getRelativeValue(time, alpha, blend, bone->_x, bone->_data._x); +} + +RTTI_IMPL(TranslateYTimeline, CurveTimeline1) + +TranslateYTimeline::TranslateYTimeline(size_t frameCount, size_t bezierCount, int boneIndex) : CurveTimeline1( + frameCount, bezierCount), + _boneIndex(boneIndex) { + PropertyId ids[] = {((PropertyId) Property_Y << 32) | boneIndex}; + setPropertyIds(ids, 1); +} + +TranslateYTimeline::~TranslateYTimeline() { +} + +void TranslateYTimeline::apply(Skeleton &skeleton, float lastTime, float time, Vector *pEvents, float alpha, + MixBlend blend, MixDirection direction) { + SP_UNUSED(lastTime); + SP_UNUSED(pEvents); + SP_UNUSED(direction); + + Bone *bone = skeleton._bones[_boneIndex]; + if (bone->_active) bone->_y = getRelativeValue(time, alpha, blend, bone->_y, bone->_data._y); +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Triangulator.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Triangulator.cpp new file mode 100644 index 0000000..336c6f5 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Triangulator.cpp @@ -0,0 +1,285 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +using namespace spine; + +Triangulator::~Triangulator() { + ContainerUtil::cleanUpVectorOfPointers(_convexPolygons); + ContainerUtil::cleanUpVectorOfPointers(_convexPolygonsIndices); +} + +Vector &Triangulator::triangulate(Vector &vertices) { + size_t vertexCount = vertices.size() >> 1; + + Vector &indices = _indices; + indices.clear(); + indices.ensureCapacity(vertexCount); + indices.setSize(vertexCount, 0); + for (int i = 0; i < (int) vertexCount; ++i) { + indices[i] = i; + } + + Vector &isConcaveArray = _isConcaveArray; + isConcaveArray.ensureCapacity(vertexCount); + isConcaveArray.setSize(vertexCount, 0); + for (int i = 0, n = (int) vertexCount; i < n; ++i) { + isConcaveArray[i] = isConcave(i, (int) vertexCount, vertices, indices); + } + + Vector &triangles = _triangles; + triangles.clear(); + triangles.ensureCapacity(MathUtil::max((int) 0, (int) vertexCount - 2) << 2); + + while (vertexCount > 3) { + // Find ear tip. + size_t previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) { + if (!isConcaveArray[i]) { + int p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1; + float p1x = vertices[p1], p1y = vertices[p1 + 1]; + float p2x = vertices[p2], p2y = vertices[p2 + 1]; + float p3x = vertices[p3], p3y = vertices[p3 + 1]; + for (size_t ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { + if (!isConcaveArray[ii]) continue; + + int v = indices[ii] << 1; + float &vx = vertices[v], vy = vertices[v + 1]; + if (positiveArea(p3x, p3y, p1x, p1y, vx, vy)) { + if (positiveArea(p1x, p1y, p2x, p2y, vx, vy)) { + if (positiveArea(p2x, p2y, p3x, p3y, vx, vy)) { + goto break_outer;// break outer; + } + } + } + } + break; + } + break_outer: + + if (next == 0) { + do { + if (!isConcaveArray[i]) break; + i--; + } while (i > 0); + break; + } + + previous = i; + i = next; + next = (next + 1) % vertexCount; + } + + // Cut ear tip. + triangles.add(indices[(vertexCount + i - 1) % vertexCount]); + triangles.add(indices[i]); + triangles.add(indices[(i + 1) % vertexCount]); + indices.removeAt(i); + isConcaveArray.removeAt(i); + vertexCount--; + + int previousIndex = (int) ((vertexCount + i - 1) % vertexCount); + int nextIndex = (int) (i == vertexCount ? 0 : i); + isConcaveArray[previousIndex] = isConcave(previousIndex, (int) vertexCount, vertices, indices); + isConcaveArray[nextIndex] = isConcave(nextIndex, (int) vertexCount, vertices, indices); + } + + if (vertexCount == 3) { + triangles.add(indices[2]); + triangles.add(indices[0]); + triangles.add(indices[1]); + } + + return triangles; +} + +Vector *> &Triangulator::decompose(Vector &vertices, Vector &triangles) { + Vector *> &convexPolygons = _convexPolygons; + for (size_t i = 0, n = convexPolygons.size(); i < n; ++i) + _polygonPool.free(convexPolygons[i]); + convexPolygons.clear(); + + Vector *> &convexPolygonsIndices = _convexPolygonsIndices; + for (size_t i = 0, n = convexPolygonsIndices.size(); i < n; ++i) + _polygonIndicesPool.free(convexPolygonsIndices[i]); + convexPolygonsIndices.clear(); + + Vector *polygonIndices = _polygonIndicesPool.obtain(); + polygonIndices->clear(); + + Vector *polygon = _polygonPool.obtain(); + polygon->clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastwinding = 0; + for (size_t i = 0, n = triangles.size(); i < n; i += 3) { + int t1 = triangles[i] << 1, t2 = triangles[i + 1] << 1, t3 = triangles[i + 2] << 1; + float x1 = vertices[t1], y1 = vertices[t1 + 1]; + float x2 = vertices[t2], y2 = vertices[t2 + 1]; + float x3 = vertices[t3], y3 = vertices[t3 + 1]; + + // If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan). + bool merged = false; + if (fanBaseIndex == t1) { + size_t o = polygon->size() - 4; + Vector &p = *polygon; + int winding1 = winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3); + int winding2 = winding(x3, y3, p[0], p[1], p[2], p[3]); + if (winding1 == lastwinding && winding2 == lastwinding) { + polygon->add(x3); + polygon->add(y3); + polygonIndices->add(t3); + merged = true; + } + } + + // Otherwise make this triangle the new base. + if (!merged) { + if (polygon->size() > 0) { + convexPolygons.add(polygon); + convexPolygonsIndices.add(polygonIndices); + } else { + _polygonPool.free(polygon); + _polygonIndicesPool.free(polygonIndices); + } + + polygon = _polygonPool.obtain(); + polygon->clear(); + polygon->add(x1); + polygon->add(y1); + polygon->add(x2); + polygon->add(y2); + polygon->add(x3); + polygon->add(y3); + polygonIndices = _polygonIndicesPool.obtain(); + polygonIndices->clear(); + polygonIndices->add(t1); + polygonIndices->add(t2); + polygonIndices->add(t3); + lastwinding = winding(x1, y1, x2, y2, x3, y3); + fanBaseIndex = t1; + } + } + + if (polygon->size() > 0) { + convexPolygons.add(polygon); + convexPolygonsIndices.add(polygonIndices); + } + + // Go through the list of polygons and try to merge the remaining triangles with the found triangle fans. + for (size_t i = 0, n = convexPolygons.size(); i < n; ++i) { + polygonIndices = convexPolygonsIndices[i]; + + if (polygonIndices->size() == 0) continue; + int firstIndex = (*polygonIndices)[0]; + int lastIndex = (*polygonIndices)[polygonIndices->size() - 1]; + + polygon = convexPolygons[i]; + size_t o = polygon->size() - 4; + Vector &p = *polygon; + float prevPrevX = p[o], prevPrevY = p[o + 1]; + float prevX = p[o + 2], prevY = p[o + 3]; + float firstX = p[0], firstY = p[1]; + float secondX = p[2], secondY = p[3]; + int winding0 = winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (size_t ii = 0; ii < n; ++ii) { + if (ii == i) continue; + + Vector *otherIndicesP = convexPolygonsIndices[ii]; + Vector &otherIndices = *otherIndicesP; + + if (otherIndices.size() != 3) continue; + + int otherFirstIndex = otherIndices[0]; + int otherSecondIndex = otherIndices[1]; + int otherLastIndex = otherIndices[2]; + + Vector *otherPolyP = convexPolygons[ii]; + Vector &otherPoly = *otherPolyP; + + float x3 = otherPoly[otherPoly.size() - 2], y3 = otherPoly[otherPoly.size() - 1]; + + if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue; + + int winding1 = winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3); + int winding2 = winding(x3, y3, firstX, firstY, secondX, secondY); + if (winding1 == winding0 && winding2 == winding0) { + otherPoly.clear(); + otherIndices.clear(); + polygon->add(x3); + polygon->add(y3); + polygonIndices->add(otherLastIndex); + prevPrevX = prevX; + prevPrevY = prevY; + prevX = x3; + prevY = y3; + ii = 0; + } + } + } + + // Remove empty polygons that resulted from the merge step above. + for (int i = (int) convexPolygons.size() - 1; i >= 0; --i) { + polygon = convexPolygons[i]; + if (polygon->size() == 0) { + convexPolygons.removeAt(i); + _polygonPool.free(polygon); + polygonIndices = convexPolygonsIndices[i]; + convexPolygonsIndices.removeAt(i); + _polygonIndicesPool.free(polygonIndices); + } + } + + return convexPolygons; +} + +bool Triangulator::isConcave(int index, int vertexCount, Vector &vertices, Vector &indices) { + int previous = indices[(vertexCount + index - 1) % vertexCount] << 1; + int current = indices[index] << 1; + int next = indices[(index + 1) % vertexCount] << 1; + + return !positiveArea(vertices[previous], vertices[previous + 1], + vertices[current], vertices[current + 1], + vertices[next], vertices[next + 1]); +} + +bool Triangulator::positiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; +} + +int Triangulator::winding(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { + float px = p2x - p1x, py = p2y - p1y; + return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Updatable.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Updatable.cpp new file mode 100644 index 0000000..8e874a2 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/Updatable.cpp @@ -0,0 +1,40 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +using namespace spine; + +RTTI_IMPL_NOPARENT(Updatable) + +Updatable::Updatable() { +} + +Updatable::~Updatable() { +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/VertexAttachment.cpp b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/VertexAttachment.cpp new file mode 100644 index 0000000..a84f9a7 --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/Public/spine-cpp/src/spine/VertexAttachment.cpp @@ -0,0 +1,167 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated April 5, 2025. Replaces all prior versions. + * + * Copyright (c) 2013-2025, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include + +#include + +#include +#include + +using namespace spine; + +RTTI_IMPL(VertexAttachment, Attachment) + +VertexAttachment::VertexAttachment(const String &name) : Attachment(name), _worldVerticesLength(0), + _timelineAttachment(this), _id(getNextID()) { +} + +VertexAttachment::~VertexAttachment() { +} + +void VertexAttachment::computeWorldVertices(Slot &slot, Vector &worldVertices) { + computeWorldVertices(slot, 0, _worldVerticesLength, worldVertices, 0); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, float *worldVertices) { + computeWorldVertices(slot, 0, _worldVerticesLength, worldVertices, 0); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, Vector &worldVertices, + size_t offset, size_t stride) { + computeWorldVertices(slot, start, count, worldVertices.buffer(), offset, stride); +} + +void VertexAttachment::computeWorldVertices(Slot &slot, size_t start, size_t count, float *worldVertices, size_t offset, + size_t stride) { + count = offset + (count >> 1) * stride; + Skeleton &skeleton = slot._bone._skeleton; + Vector *deformArray = &slot.getDeform(); + Vector *vertices = &_vertices; + Vector &bones = _bones; + if (bones.size() == 0) { + if (deformArray->size() > 0) vertices = deformArray; + + Bone &bone = slot._bone; + float x = bone._worldX; + float y = bone._worldY; + float a = bone._a, b = bone._b, c = bone._c, d = bone._d; + for (size_t vv = start, w = offset; w < count; vv += 2, w += stride) { + float vx = (*vertices)[vv]; + float vy = (*vertices)[vv + 1]; + worldVertices[w] = vx * a + vy * b + x; + worldVertices[w + 1] = vx * c + vy * d + y; + } + return; + } + + int v = 0, skip = 0; + for (size_t i = 0; i < start; i += 2) { + int n = (int) bones[v]; + v += n + 1; + skip += n; + } + + Vector &skeletonBones = skeleton.getBones(); + if (deformArray->size() == 0) { + for (size_t w = offset, b = skip * 3; w < count; w += stride) { + float wx = 0, wy = 0; + int n = (int) bones[v++]; + n += v; + for (; v < n; v++, b += 3) { + Bone *boneP = skeletonBones[bones[v]]; + Bone &bone = *boneP; + float vx = (*vertices)[b]; + float vy = (*vertices)[b + 1]; + float weight = (*vertices)[b + 2]; + wx += (vx * bone._a + vy * bone._b + bone._worldX) * weight; + wy += (vx * bone._c + vy * bone._d + bone._worldY) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } else { + for (size_t w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { + float wx = 0, wy = 0; + int n = (int) bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) { + Bone *boneP = skeletonBones[bones[v]]; + Bone &bone = *boneP; + float vx = (*vertices)[b] + (*deformArray)[f]; + float vy = (*vertices)[b + 1] + (*deformArray)[f + 1]; + float weight = (*vertices)[b + 2]; + wx += (vx * bone._a + vy * bone._b + bone._worldX) * weight; + wy += (vx * bone._c + vy * bone._d + bone._worldY) * weight; + } + worldVertices[w] = wx; + worldVertices[w + 1] = wy; + } + } +} + +int VertexAttachment::getId() { + return _id; +} + +Vector &VertexAttachment::getBones() { + return _bones; +} + +Vector &VertexAttachment::getVertices() { + return _vertices; +} + +size_t VertexAttachment::getWorldVerticesLength() { + return _worldVerticesLength; +} + +void VertexAttachment::setWorldVerticesLength(size_t inValue) { + _worldVerticesLength = inValue; +} + +Attachment *VertexAttachment::getTimelineAttachment() { + return _timelineAttachment; +} + +void VertexAttachment::setTimelineAttachment(Attachment *attachment) { + _timelineAttachment = attachment; +} + +int VertexAttachment::getNextID() { + static int nextID = 0; + return nextID++; +} + +void VertexAttachment::copyTo(VertexAttachment *other) { + other->_bones.clearAndAddAll(this->_bones); + other->_vertices.clearAndAddAll(this->_vertices); + other->_worldVerticesLength = this->_worldVerticesLength; + other->_timelineAttachment = this->_timelineAttachment; +} diff --git a/Plugins/SpinePlugin/Source/SpinePlugin/SpinePlugin.Build.cs b/Plugins/SpinePlugin/Source/SpinePlugin/SpinePlugin.Build.cs new file mode 100644 index 0000000..a24ff4b --- /dev/null +++ b/Plugins/SpinePlugin/Source/SpinePlugin/SpinePlugin.Build.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class SpinePlugin : ModuleRules + { + public SpinePlugin(ReadOnlyTargetRules target) : base(target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public/spine-cpp/include")); + + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "Private")); + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "Public/spine-cpp/include")); + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "ProceduralMeshComponent", "UMG", "Slate", "SlateCore" }); + PublicDefinitions.Add("SPINE_UE4"); + } + } +} diff --git a/Plugins/SpinePlugin/SpinePlugin.uplugin b/Plugins/SpinePlugin/SpinePlugin.uplugin new file mode 100644 index 0000000..49b66e7 --- /dev/null +++ b/Plugins/SpinePlugin/SpinePlugin.uplugin @@ -0,0 +1,34 @@ +{ + "FileVersion" : 3, + "Version" : 1, + "VersionName" : "1.0", + "FriendlyName" : "Spine Plugin", + "Description" : "A plugin to load and render Spine animations in Unreal Engine", + "Category" : "2D", + "CreatedBy" : "Esoteric Software", + "CreatedByURL" : "http://esotericsoftware.com", + "DocsURL" : "", + "MarketplaceURL" : "", + "SupportURL" : "", + "EnabledByDefault" : true, + "CanContainContent" : true, + "IsBetaVersion" : false, + "Installed" : false, + "Modules": [ + { + "Name": "SpinePlugin", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "SpineEditorPlugin", + "Type": "Editor" + } + ], + "Plugins": [ + { + "Name": "ProceduralMeshComponent", + "Enabled": true + } + ] +} diff --git a/Source/BusyRabbit/BusyRabbit.Build.cs b/Source/BusyRabbit/BusyRabbit.Build.cs index 9370844..29dcf77 100644 --- a/Source/BusyRabbit/BusyRabbit.Build.cs +++ b/Source/BusyRabbit/BusyRabbit.Build.cs @@ -11,7 +11,7 @@ public class BusyRabbit : ModuleRules PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "Paper2D", "UMG", "Slate", - "GameplayAbilities", "GameplayTags","GameplayTasks" + "GameplayAbilities", "GameplayTags","GameplayTasks", "SpinePlugin" }); PrivateDependencyModuleNames.AddRange(new string[] { }); diff --git a/Source/BusyRabbit/Private/BusyPlayerController.cpp b/Source/BusyRabbit/Private/BusyPlayerController.cpp index 708f1b2..cfe6cb8 100644 --- a/Source/BusyRabbit/Private/BusyPlayerController.cpp +++ b/Source/BusyRabbit/Private/BusyPlayerController.cpp @@ -4,7 +4,7 @@ void ABusyPlayerController::BeginPlay(){ Super::BeginPlay(); - bShowMouseCursor = true; // ʾ + bShowMouseCursor = true; FInputModeGameAndUI InputMode; InputMode.SetHideCursorDuringCapture(false); InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); diff --git a/Source/BusyRabbit/Private/Level/Actor/BusyPawnBase.cpp b/Source/BusyRabbit/Private/Level/Actor/BusyPawnBase.cpp new file mode 100644 index 0000000..f614b8d --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Actor/BusyPawnBase.cpp @@ -0,0 +1,42 @@ +#include "Level/Actor/BusyPawnBase.h" +#include "Components/SphereComponent.h" +#include "SpineSkeletonRendererComponent.h" +#include "SpineSkeletonAnimationComponent.h" +#include "Level/Actor/Components/BusyPawnMovement.h" +#include "SpineBoneFollowerComponent.h" + +ABusyPawnBase::ABusyPawnBase() +{ + RootScene = CreateDefaultSubobject(TEXT("RootScene")); + SpineRoot = CreateDefaultSubobject(TEXT("SpineRoot")); + SphereComponent = CreateDefaultSubobject(TEXT("SphereComponent")); + SpineRenderComponent = CreateDefaultSubobject(TEXT("SpineRenderComponent")); + SpineAnimationComponent = CreateDefaultSubobject(TEXT("SpineAnimationComponent")); + MovementComponent = CreateDefaultSubobject(TEXT("MovementComponent")); + + RootComponent = RootScene; + SpineRoot->SetupAttachment(RootScene); + SphereComponent->SetupAttachment(SpineRoot); + SpineRenderComponent->SetupAttachment(SpineRoot); + + + SpineRoot->SetRelativeRotation(FRotator(0, 0, -90)); + +} + +void ABusyPawnBase::BeginPlay() +{ + Super::BeginPlay(); + SpineAnimationComponent->SetSkin(DefaultSkinName); + SpineAnimationComponent->SetAnimation(0, DefaultAnimationName, true); +} + +void ABusyPawnBase::UpdateMoveDirection_Implementation(const FVector2D& InDirection) +{ + +} + +float ABusyPawnBase::GetSpeed_Implementation()const +{ + return 280; +} diff --git a/Source/BusyRabbit/Private/Level/Actor/BusyPlayerRole.cpp b/Source/BusyRabbit/Private/Level/Actor/BusyPlayerRole.cpp new file mode 100644 index 0000000..451bae0 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Actor/BusyPlayerRole.cpp @@ -0,0 +1,18 @@ +#include "Level/Actor/BusyPlayerRole.h" + +#include "Camera/CameraComponent.h" +#include "GameFramework/SpringArmComponent.h" + +ABusyPlayerRole::ABusyPlayerRole() +{ + CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); + SpringArmComponent = CreateDefaultSubobject(TEXT("SpringArmComponent")); + + SpringArmComponent->SetupAttachment(SpineRoot); + CameraComponent->SetupAttachment(SpringArmComponent); + + CameraComponent->SetOrthoWidth(3840); + CameraComponent->SetProjectionMode(ECameraProjectionMode::Orthographic); + + SpringArmComponent->SetRelativeRotation(FRotator(0, -90.0, 0.0)); +} diff --git a/Source/BusyRabbit/Private/Level/Actor/Components/BusyPawnMovement.cpp b/Source/BusyRabbit/Private/Level/Actor/Components/BusyPawnMovement.cpp new file mode 100644 index 0000000..a8a90b3 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Actor/Components/BusyPawnMovement.cpp @@ -0,0 +1,68 @@ +#include "Level/Actor/Components/BusyPawnMovement.h" + + + +UBusyPawnMovement::UBusyPawnMovement() +{ + this->PrimaryComponentTick.bCanEverTick = true; +} + +void UBusyPawnMovement::MoveTo(const FVector2D& Target) +{ + MoveTargetLocation = Target; +} + +FVector2D UBusyPawnMovement::GetMoveDirection()const +{ + if (AActor *Owner = GetOwner()) + { + const FVector CurrentLocation = Owner->GetActorLocation(); + FVector2D Direction = MoveTargetLocation - FVector2D(CurrentLocation); + if (Direction.Normalize()) + { + return Direction; + } + else + { + return FVector2D(); + } + } + return FVector2D(); +} + +void UBusyPawnMovement::TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + AActor* Owner = GetOwner(); + const IBusyMovable* Movable = Cast(Owner); + if (!Owner || !Movable) return; + + FVector NewLocation; + const float MoveDistance = DeltaTime * Movable->Execute_GetSpeed(Owner); + const FVector CurrentLocation = Owner->GetActorLocation(); + + FVector2D MoveDirection = MoveTargetLocation - FVector2D(CurrentLocation); + + if (MoveDistance * MoveDistance > MoveDirection.SizeSquared()) // 已经到达目的地 + { + NewLocation = FVector(MoveTargetLocation, CurrentLocation.Z); + } + else + { + if (MoveDirection.Normalize()) + { + NewLocation = CurrentLocation + FVector(MoveDirection * MoveDistance, 0); + } + else + { + NewLocation = CurrentLocation; + } + } + if (!NewLocation.Equals(CurrentLocation)) + { + Owner->SetActorLocation(NewLocation, true); + } + Movable->Execute_UpdateMoveDirection(Owner, GetMoveDirection()); +} + diff --git a/Source/BusyRabbit/Private/Level/Generator/MitchellBestCandidate.cpp b/Source/BusyRabbit/Private/Level/Generator/MitchellBestCandidate.cpp new file mode 100644 index 0000000..7ee2a7c --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Generator/MitchellBestCandidate.cpp @@ -0,0 +1,88 @@ +#include "Level/Generator/MitchellBestCandidate.h" +#include "Math/UnrealMathUtility.h" + +UMitchellBestCandidate::UMitchellBestCandidate() +{ + // 初始化随机数生成器 + RandomStream = FRandomStream(FMath::Rand()); +} + +TArray UMitchellBestCandidate::GeneratePoints(int32 NumPoints, float Width, float Height, int32 NumCandidates)const +{ + TArray Points; + + // 参数验证 + if (NumPoints <= 0 || Width <= 0 || Height <= 0 || NumCandidates <= 0) + { + UE_LOG(LogTemp, Warning, TEXT("Invalid parameters for GeneratePoints")); + return Points; + } + + // 步骤1: 添加第一个随机点作为起始点 + FVector2D FirstPoint( + RandomStream.FRandRange(0.0f, Width), + RandomStream.FRandRange(0.0f, Height) + ); + Points.Add(FirstPoint); + + // 步骤2: 为剩余的每个点生成候选点并选择最佳的一个 + for (int32 i = 1; i < NumPoints; i++) + { + FVector2D BestCandidate; + float BestDistance = -1.0f; + + // 为当前点生成NumCandidates个候选点 + for (int32 j = 0; j < NumCandidates; j++) + { + // 生成随机候选点 + FVector2D Candidate( + RandomStream.FRandRange(0.0f, Width), + RandomStream.FRandRange(0.0f, Height) + ); + + // 计算候选点到现有点集的最小距离 + float MinDist = MinDistanceToSet(Candidate, Points); + + // 选择距离现有点集最远的候选点 + if (MinDist > BestDistance) + { + BestDistance = MinDist; + BestCandidate = Candidate; + } + } + + // 将最佳候选点添加到点集中 + Points.Add(BestCandidate); + } + + return Points; +} + +float UMitchellBestCandidate::Distance(const FVector2D& A, const FVector2D& B) +{ + // 使用UE内置的向量距离计算函数 + return FVector2D::Distance(A, B); +} + +float UMitchellBestCandidate::MinDistanceToSet(const FVector2D& Point, const TArray& PointSet) +{ + // 处理空点集的情况 + if (PointSet.Num() == 0) + { + return FLT_MAX; + } + + float MinDist = FLT_MAX; + + // 遍历点集中的所有点,找到最小距离 + for (const FVector2D& ExistingPoint : PointSet) + { + float Dist = Distance(Point, ExistingPoint); + if (Dist < MinDist) + { + MinDist = Dist; + } + } + + return MinDist; +} diff --git a/Source/BusyRabbit/Private/Level/Generator/TerrainGeneratorBase.cpp b/Source/BusyRabbit/Private/Level/Generator/TerrainGeneratorBase.cpp new file mode 100644 index 0000000..35df170 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Generator/TerrainGeneratorBase.cpp @@ -0,0 +1,158 @@ +#include "Level/Generator/TerrainGeneratorBase.h" + +UTerrainGeneratorBase::UTerrainGeneratorBase() +{ +} + +int32 UTerrainGeneratorBase::AddTerrain(const FGameplayTag& TerrainTag) +{ + FTerrainNodeInfo NewNode; + NewNode.TerrainTag = TerrainTag; + NewNode.Probability = 1.0f; + NewNode.Weight = 1.0f; + NewNode.ParentHandle = -1; + NewNode.bIsLeafNode = true; + + int32 Handle = NextHandle++; + TerrainNodes.Add(Handle, NewNode); + + return Handle; +} + +void UTerrainGeneratorBase::SetWeight(int32 Handle, float Weight) +{ + if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)) + { + Node->Weight = FMath::Max(0.0f, Weight); + } +} + +void UTerrainGeneratorBase::SetProbability(int32 Handle, float Probability) +{ + if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)) + { + Node->Probability = FMath::Clamp(Probability, 0.0f, 1.0f); + } +} + +void UTerrainGeneratorBase::SetExclusive(const TArray& Handles){ + if (Handles.Num() <= 1) return; + ExclusiveGroups.Add(TSet(Handles)); +} + +void UTerrainGeneratorBase::BindChildTerrain(int32 ParentHandle, const TArray& ChildHandles) +{ + if (FTerrainNodeInfo* ParentNode = TerrainNodes.Find(ParentHandle)) + { + // 设置父节点为非叶子节点 + ParentNode->bIsLeafNode = false; + + // 添加子节点 + ParentNode->ChildHandles = ChildHandles; + + // 设置子节点的父节点 + for (int32 ChildHandle : ChildHandles) + { + if (FTerrainNodeInfo* ChildNode = TerrainNodes.Find(ChildHandle)) + { + ChildNode->ParentHandle = ParentHandle; + } + } + } +} + +FGameplayTag UTerrainGeneratorBase::GetFinalTerrainTag(const int32 Handle) const{ + if (const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)){ + return Node->TerrainTag; + } + return FGameplayTag(); +} + +TArray UTerrainGeneratorBase::GetValidLeafNodes() const +{ + TSet ValidNodes; + GetPreCheckVisibleHandles(ValidNodes); + + // 检查互斥关系, 待实现 + TSet FinalNodes; + for (int32 Handle : ValidNodes){ + if (FinalNodes.Find(Handle)) continue; + FinalNodes.Add(Handle); + } + + // 只保留叶子节点 + TArray LeafNodes; + for (int32 Handle : FinalNodes) + { + const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle); + if (Node && Node->bIsLeafNode) + { + LeafNodes.Add(Handle); + } + } + + return LeafNodes; +} + + +void UTerrainGeneratorBase::GetPreCheckVisibleHandles(TSet& VisibleHandles)const{ + bool bShouldAppear; + TArray AppearanceCheckResult; + const int TerrainNodeCount = TerrainNodes.Num(); + AppearanceCheckResult.Init(true, TerrainNodeCount); + + for (const auto& Pair : TerrainNodes) { + const int Handle = Pair.Key; + const FTerrainNodeInfo* Node = &Pair.Value; + + if (AppearanceCheckResult[Handle]) { + bShouldAppear = FMath::FRand() < Node->Probability; + AppearanceCheckResult[Handle] = bShouldAppear; + } + else { + bShouldAppear = false; + } + + if (!bShouldAppear) { + for (const int32 ChildHandle : Pair.Value.ChildHandles) { + AppearanceCheckResult[ChildHandle] = false; + } + } + } + + for (int i = 0; i < TerrainNodeCount; ++i) { + if (AppearanceCheckResult[i]) { + VisibleHandles.Add(i); + } + } +} + + +TArray UTerrainGeneratorBase::GenerateMap(int32 Width, int32 Height) +{ + TArray Map; + Map.SetNum(Width * Height); + + // 获取有效的叶子节点 + TArray ValidLeafNodes = GetValidLeafNodes(); + + if (ValidLeafNodes.Num() == 0) + { + UE_LOG(LogTemp, Error, TEXT("No valid terrain nodes to generate map!")); + return Map; + } + + if (GenerateWithNodes(Map, Width, Height, ValidLeafNodes)) + { + return Map; + } + else + { + return TArray(); + } +} + +bool UTerrainGeneratorBase::GenerateWithNodes(TArray& Map, int32 Width, int32 Height, const TArray& ValidLeafNodes) +{ + return false; +} diff --git a/Source/BusyRabbit/Private/Level/Generator/VoronoiDiagram.cpp b/Source/BusyRabbit/Private/Level/Generator/VoronoiDiagram.cpp new file mode 100644 index 0000000..f9f2bc7 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Generator/VoronoiDiagram.cpp @@ -0,0 +1,178 @@ +#include "Level/Generator/VoronoiDiagram.h" +#include "Math/UnrealMathUtility.h" +#include "Math/Vector2D.h" + +UVoronoiDiagram::UVoronoiDiagram() +{ + // 默认构造函数 +} + +TArray UVoronoiDiagram::GenerateDiagram(const TArray& Sites, float Width, float Height) +{ + TArray Cells; + + // 参数验证 + if (Sites.Num() == 0 || Width <= 0 || Height <= 0) + { + UE_LOG(LogTemp, Warning, TEXT("Invalid parameters for GenerateDiagram")); + return Cells; + } + + // 为每个生成点创建泰森多边形单元 + for (int32 i = 0; i < Sites.Num(); i++) + { + FVoronoiCell Cell; + Cell.Site = Sites[i]; + + // 计算边界框的四个角点(作为初始多边形) + TArray Bounds = { + FVector2D(0, 0), + FVector2D(Width, 0), + FVector2D(Width, Height), + FVector2D(0, Height) + }; + + // 简化的泰森多边形生成算法 + // 注意:这是一个简化实现,完整的泰森多边形算法更复杂 + TArray Polygon = Bounds; + + // 对每个其他生成点进行裁剪(使用垂直平分线) + for (int32 j = 0; j < Sites.Num(); j++) + { + if (i == j) continue; + + FVector2D Midpoint, Direction; + if (CalculatePerpendicularBisector(Sites[i], Sites[j], Midpoint, Direction)) + { + // 这里简化处理,实际泰森多边形算法需要更复杂的几何计算 + // 使用中点作为参考点来调整多边形边界 + } + } + + // 裁剪多边形到边界范围内 + Cell.Vertices = ClipPolygonToBounds(Polygon, Width, Height); + Cells.Add(Cell); + } + + return Cells; +} + +int32 UVoronoiDiagram::FindCellIndex(const FVector2D& Point, const TArray& Sites) +{ + // 处理空点集的情况 + if (Sites.Num() == 0) + { + return -1; + } + + int32 ClosestIndex = 0; + float MinDistance = FVector2D::Distance(Point, Sites[0]); + + // 遍历所有生成点,找到距离最近的点 + for (int32 i = 1; i < Sites.Num(); i++) + { + float Distance = FVector2D::Distance(Point, Sites[i]); + if (Distance < MinDistance) + { + MinDistance = Distance; + ClosestIndex = i; + } + } + + return ClosestIndex; +} + +TArray UVoronoiDiagram::ConvertToGrid(const TArray& Sites, int32 Width, int32 Height, float RegionWidth, float RegionHeight) +{ + TArray Grid; + Grid.SetNum(Width * Height); + + // 参数验证 + if (Sites.Num() == 0 || Width <= 0 || Height <= 0 || RegionWidth <= 0 || RegionHeight <= 0) + { + UE_LOG(LogTemp, Warning, TEXT("Invalid parameters for ConvertToGrid")); + return Grid; + } + + // 计算每个网格单元格的大小 + float CellWidth = RegionWidth / Width; + float CellHeight = RegionHeight / Height; + + // 遍历网格中的每个单元格 + for (int32 Y = 0; Y < Height; Y++) + { + for (int32 X = 0; X < Width; X++) + { + // 计算单元格中心点坐标 + FVector2D GridPoint( + (X + 0.5f) * CellWidth, + (Y + 0.5f) * CellHeight + ); + + // 查找该点所在的泰森多边形单元 + int32 CellIndex = FindCellIndex(GridPoint, Sites); + Grid[Y * Width + X] = CellIndex; + } + } + + return Grid; +} + +bool UVoronoiDiagram::CalculatePerpendicularBisector(const FVector2D& A, const FVector2D& B, FVector2D& OutMidpoint, FVector2D& OutDirection) +{ + // 计算两点中点 + OutMidpoint = (A + B) * 0.5f; + FVector2D AB = B - A; + + // 检查两点是否重合 + if (AB.SizeSquared() < KINDA_SMALL_NUMBER) + { + return false; + } + + // 计算垂直方向向量(AB向量的垂直向量) + OutDirection = FVector2D(-AB.Y, AB.X); + OutDirection.Normalize(); + + return true; +} + +bool UVoronoiDiagram::CalculateLineIntersection(const FVector2D& P1, const FVector2D& D1, const FVector2D& P2, const FVector2D& D2, FVector2D& OutIntersection) +{ + // 检查两条直线是否平行(叉积接近0) + float Cross = D1.X * D2.Y - D1.Y * D2.X; + if (FMath::Abs(Cross) < KINDA_SMALL_NUMBER) + { + return false; + } + + // 计算交点参数 + FVector2D P2MinusP1 = P2 - P1; + float T = (P2MinusP1.X * D2.Y - P2MinusP1.Y * D2.X) / Cross; + + // 计算交点坐标 + OutIntersection = P1 + D1 * T; + return true; +} + +bool UVoronoiDiagram::IsPointInBounds(const FVector2D& Point, float Width, float Height) +{ + // 检查点是否在矩形边界内 + return Point.X >= 0 && Point.X <= Width && Point.Y >= 0 && Point.Y <= Height; +} + +TArray UVoronoiDiagram::ClipPolygonToBounds(const TArray& Polygon, float Width, float Height) +{ + TArray ClippedPolygon; + + // 遍历多边形所有顶点,只保留在边界内的顶点 + for (const FVector2D& Vertex : Polygon) + { + if (IsPointInBounds(Vertex, Width, Height)) + { + ClippedPolygon.Add(Vertex); + } + } + + return ClippedPolygon; +} diff --git a/Source/BusyRabbit/Private/Level/Generator/VoronoiTerrainGenerator.cpp b/Source/BusyRabbit/Private/Level/Generator/VoronoiTerrainGenerator.cpp new file mode 100644 index 0000000..6d3954c --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Generator/VoronoiTerrainGenerator.cpp @@ -0,0 +1,175 @@ +#include "Level/Generator/VoronoiTerrainGenerator.h" +#include "Math/UnrealMathUtility.h" +#include "Math/RandomStream.h" + +UVoronoiTerrainGenerator::UVoronoiTerrainGenerator() +{ + // 初始化默认参数 + NextHandle = 0; + VoronoiRegionCount = 50; // 默认50个泰森多边形区域 + MitchellCandidateCount = 10; // 默认每个点10个候选点 + + // 创建算法实例 + MitchellGenerator = NewObject(); + VoronoiGenerator = NewObject(); +} + + + + +bool UVoronoiTerrainGenerator::GenerateWithNodes( + TArray& Map, int32 Width, int32 Height, + const TArray& ValidLeafNodes +){ + // 使用泰森多边形算法生成地图 + GenerateWithVoronoi(Map, Width, Height, ValidLeafNodes); + return true; +} + + + +void UVoronoiTerrainGenerator::SetVoronoiRegionCount(int32 Count) +{ + // 设置泰森多边形区域数量,确保至少为1 + VoronoiRegionCount = FMath::Max(1, Count); +} + +void UVoronoiTerrainGenerator::SetMitchellCandidateCount(int32 Count) +{ + // 设置米切尔候选点数,确保至少为1 + MitchellCandidateCount = FMath::Max(1, Count); +} + + + + +void UVoronoiTerrainGenerator::GenerateWithVoronoi(TArray& Map, int32 Width, int32 Height, + const TArray& ValidLeafNodes) const +{ + // 检查是否有有效的叶子节点 + if (ValidLeafNodes.Num() == 0) + { + return; + } + + FRandomStream RandomStream(FMath::Rand()); + + // 步骤1: 计算所有有效叶子节点的总权重 + float TotalWeight = 0.0f; + for (int32 Handle : ValidLeafNodes) + { + if (const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)) + { + TotalWeight += Node->Weight; + } + } + + // 检查总权重是否有效 + if (TotalWeight <= 0.0f) + { + return; + } + + // 步骤2: 使用米切尔最佳候选算法生成均匀分布的点集 + TArray Sites = MitchellGenerator->GeneratePoints( + VoronoiRegionCount, Width, Height, MitchellCandidateCount + ); + + // 步骤3: 将泰森多边形转换为网格表示 + TArray VoronoiGrid = UVoronoiDiagram::ConvertToGrid(Sites, Width, Height, Width, Height); + + // 步骤4: 根据权重分配地形类型到各个泰森多边形区域 + TArray RegionToTerrain; + RegionToTerrain.SetNum(Sites.Num()); + + // 为每个泰森多边形区域分配地形类型 + for (int32 RegionIndex = 0; RegionIndex < Sites.Num(); RegionIndex++) + { + float RandValue = RandomStream.FRand(); + float CurrentWeight = 0.0f; + + // 根据权重比例分配地形 + for (int32 i = 0; i < ValidLeafNodes.Num(); i++) + { + if (const FTerrainNodeInfo* Node = TerrainNodes.Find(ValidLeafNodes[i])) + { + CurrentWeight += Node->Weight / TotalWeight; + if (RandValue <= CurrentWeight) + { + RegionToTerrain[RegionIndex] = i; + break; + } + } + } + } + + // 步骤5: 填充地图数据 + for (int32 Y = 0; Y < Height; Y++) + { + for (int32 X = 0; X < Width; X++) + { + int32 Index = Y * Width + X; + int32 RegionIndex = VoronoiGrid[Index]; + + // 确保区域索引有效 + if (RegionIndex >= 0 && RegionIndex < RegionToTerrain.Num()) + { + int32 TerrainIndex = RegionToTerrain[RegionIndex]; + // 确保地形索引有效 + if (TerrainIndex >= 0 && TerrainIndex < ValidLeafNodes.Num()) + { + FGameplayTag TerrainTag = GetFinalTerrainTag(ValidLeafNodes[TerrainIndex]); + Map[Index] = TerrainTag; + } + } + } + } + + // 步骤6: 应用平滑处理,改善地形过渡 + for (int32 Y = 1; Y < Height - 1; Y++) + { + for (int32 X = 1; X < Width - 1; X++) + { + int32 Index = Y * Width + X; + + // 统计周围8个邻居的地形类型 + TMap NeighborCount; + for (int32 dy = -1; dy <= 1; dy++) + { + for (int32 dx = -1; dx <= 1; dx++) + { + if (dx == 0 && dy == 0) continue; // 跳过自身 + + int32 NX = X + dx; + int32 NY = Y + dy; + + // 检查邻居是否在边界内 + if (NX >= 0 && NX < Width && NY >= 0 && NY < Height) + { + FGameplayTag NeighborTag = Map[NY * Width + NX]; + NeighborCount.FindOrAdd(NeighborTag)++; + } + } + } + + // 找到出现次数最多的邻居地形 + FGameplayTag MostCommonTag; + int32 MaxCount = 0; + + for (const auto& Pair : NeighborCount) + { + if (Pair.Value > MaxCount) + { + MaxCount = Pair.Value; + MostCommonTag = Pair.Key; + } + } + + // 如果周围大多数是另一种地形,则改变当前地形(平滑处理) + if (MaxCount >= 5 && MostCommonTag != Map[Index]) // 至少5个相同邻居 + { + Map[Index] = MostCommonTag; + } + } + } +} diff --git a/Source/BusyRabbit/Private/Level/LevelPlayerController.cpp b/Source/BusyRabbit/Private/Level/LevelPlayerController.cpp new file mode 100644 index 0000000..5eb92c3 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/LevelPlayerController.cpp @@ -0,0 +1,52 @@ +#include "Level/LevelPlayerController.h" +#include "Level/Actor/BusyPlayerRole.h" +#include "EnhancedInput/Public/EnhancedInputSubsystems.h" + +ALevelPlayerController::ALevelPlayerController() +{ +} + +void ALevelPlayerController::BeginPlay() +{ + Super::BeginPlay(); + bShowMouseCursor = true; + FInputModeGameAndUI InputMode; + InputMode.SetHideCursorDuringCapture(false); + InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); + SetInputMode(InputMode); + + // 注册输入 + const auto Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer()); + if (InputMapping && Subsystem) + { + Subsystem->AddMappingContext(InputMapping, 0); + } +} + +bool ALevelPlayerController::GetCursorPosition(FVector2D& Position) const +{ + float CursorX = 0.f, CursorY = 0.f; + if (GetMousePosition(CursorX, CursorY)) + { + FVector WorldLocation, WorldDirection; + if (DeprojectMousePositionToWorld(WorldLocation, WorldDirection)) + { + Position.Set(WorldLocation.X, WorldLocation.Y); + return true; + } + } + return false; +} + +void ALevelPlayerController::GetCursorHitResult(TArray& Results) const +{ +} + +void ALevelPlayerController::GetControlledRole() const +{ +} + +void ALevelPlayerController::SwitchControlledRole(ABusyPlayerRole* Target) +{ + this->SetViewTarget(Target); +} diff --git a/Source/BusyRabbit/Private/Level/LevelPlayerState.cpp b/Source/BusyRabbit/Private/Level/LevelPlayerState.cpp new file mode 100644 index 0000000..75117b3 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/LevelPlayerState.cpp @@ -0,0 +1,55 @@ +#include "Level/LevelPlayerState.h" + +#include "Level/LevelPlayerController.h" +#include "Level/Actor/BusyPlayerRole.h" + + +DEFINE_LOG_CATEGORY(LogLevelPlayerState); + + +void ALevelPlayerState::BeginPlay() +{ + Super::BeginPlay(); + UWorld* World = GetWorld(); + const auto PC = Cast(GetPlayerController()); + + if (!World || !PC) + { + UE_LOG(LogLevelPlayerState, Error, TEXT("ALevelPlayerController::BeginPlay() failed!")); + return; + } + + FVector2D SpawnLocation = GetSpawnLocation(); + for (auto PawnClass : RoleClasses) + { + FTransform SpawnTransform; + FActorSpawnParameters Params; + SpawnTransform.SetLocation(FVector(SpawnLocation, 50)); + if (auto *NewRole = Cast(World->SpawnActor(PawnClass, nullptr, Params))) + { + Roles.Add(NewRole); + } + } + + if (Roles.IsValidIndex(0)) + { + PC->SwitchControlledRole(Roles[0]); + } +} + +ABusyPlayerRole* ALevelPlayerState::GetControlledRole() const +{ + if (Roles.IsValidIndex(ControlledRoleIndex)) + { + return Roles[ControlledRoleIndex]; + } + else + { + return nullptr; + } +} + +FVector2D ALevelPlayerState::GetSpawnLocation()const +{ + return FVector2D::ZeroVector; +} diff --git a/Source/BusyRabbit/Private/Level/Map/ClimateLayerComponent.cpp b/Source/BusyRabbit/Private/Level/Map/ClimateLayerComponent.cpp new file mode 100644 index 0000000..f7bcef0 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Map/ClimateLayerComponent.cpp @@ -0,0 +1 @@ +#include "Level/Map/ClimateLayerComponent.h" diff --git a/Source/BusyRabbit/Private/Level/Map/CreatureLayerComponent.cpp b/Source/BusyRabbit/Private/Level/Map/CreatureLayerComponent.cpp new file mode 100644 index 0000000..1d0fe19 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Map/CreatureLayerComponent.cpp @@ -0,0 +1 @@ +#include "Level/Map/CreatureLayerComponent.h" diff --git a/Source/BusyRabbit/Private/Level/Map/DecorationLayerComponent.cpp b/Source/BusyRabbit/Private/Level/Map/DecorationLayerComponent.cpp new file mode 100644 index 0000000..0329493 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Map/DecorationLayerComponent.cpp @@ -0,0 +1 @@ +#include "Level/Map/DecorationLayerComponent.h" diff --git a/Source/BusyRabbit/Private/Level/Map/GameMapActor.cpp b/Source/BusyRabbit/Private/Level/Map/GameMapActor.cpp new file mode 100644 index 0000000..7b75303 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Map/GameMapActor.cpp @@ -0,0 +1,22 @@ +#include "Level/Map/GameMapActor.h" + +AGameMapActor::AGameMapActor() +{ + SceneComp = CreateDefaultSubobject(TEXT("SceneComp")); + + + TerrainLayer = CreateDefaultSubobject(TEXT("TerrainLayer")); + + + this->RootComponent = SceneComp; +} + +void AGameMapActor::BeginPlay() +{ + Super::BeginPlay(); +} + +void AGameMapActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); +} diff --git a/Source/BusyRabbit/Private/Level/Map/LightingLayerComponent.cpp b/Source/BusyRabbit/Private/Level/Map/LightingLayerComponent.cpp new file mode 100644 index 0000000..423b87a --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Map/LightingLayerComponent.cpp @@ -0,0 +1 @@ +#include "Level/Map/LightingLayerComponent.h" diff --git a/Source/BusyRabbit/Private/Level/Map/PlacementLayerComponent.cpp b/Source/BusyRabbit/Private/Level/Map/PlacementLayerComponent.cpp new file mode 100644 index 0000000..1bd86f2 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Map/PlacementLayerComponent.cpp @@ -0,0 +1 @@ +#include "Level/Map/PlacementLayerComponent.h" diff --git a/Source/BusyRabbit/Private/Level/Map/TerrainLayerComponent.cpp b/Source/BusyRabbit/Private/Level/Map/TerrainLayerComponent.cpp new file mode 100644 index 0000000..777e33c --- /dev/null +++ b/Source/BusyRabbit/Private/Level/Map/TerrainLayerComponent.cpp @@ -0,0 +1,264 @@ +#include "Level/Map/TerrainLayerComponent.h" +#include "Level/Generator/VoronoiTerrainGenerator.h" +#include "Paper2D/Classes/PaperTileLayer.h" +#include "Paper2D/Classes/PaperTileMapComponent.h" +#include "Paper2D/Classes/PaperTileSet.h" +#include "PaperTileMap.h" + + +/* 相邻四格地形信息映射到TileSet的索引 + * 以左上、右上、左下、右下地形块存在为1,不存在为0, 从左到右构建一个4位二进制整数 + * 假设原始TileSet中,0号索引左上、右上、右下为空, 对应二进制整数为0x0010 + * 那么TileSet的这个索引与相邻数据对应关系就是[0] -> 2 + * 也就是DefaultNeighborDataToIdxMappings[2] = 0 + */ +const static int DefaultNeighborDataToIdxMappings[16] = { + 12, 13, 0, 3, 8, 1, 14, 5, 15, 4, 11, 2, 9, 10, 7, 6 +}; + +const static FGameplayTag SortedTerrainsWithPriority [] = { + FGameplayTag::RequestGameplayTag("Terrain.Desert"), + FGameplayTag::RequestGameplayTag("Terrain.Forest"), + FGameplayTag::RequestGameplayTag("Terrain.Grassland"), + FGameplayTag::RequestGameplayTag("Terrain.Swamp.Land"), + FGameplayTag::RequestGameplayTag("Terrain.Swamp.Water"), + FGameplayTag::RequestGameplayTag("Terrain.Land"), + FGameplayTag::RequestGameplayTag("Terrain.Water.Deep"), + FGameplayTag::RequestGameplayTag("Terrain.Water.Shallow"), +}; + + + + + + + +static bool GenerateTerrain(const TArray& InTerrains, const TArray InPriority, int32 InMapSize, TArray& OutTerrains) +{ + // 验证输入参数 + if (InTerrains.Num() == 0 || InTerrains.Num() != InPriority.Num() || InMapSize <= 0) + { + return false; + } + + // 计算总权重 + int32 TotalPriority = 0; + for (int32 Priority : InPriority) + { + if (Priority < 0) return false; // 权重不能为负数 + TotalPriority += Priority; + } + + if (TotalPriority == 0) return false; // 总权重不能为零 + + // 准备输出数组 + OutTerrains.Empty(); + OutTerrains.SetNum(InMapSize * InMapSize); + + // 计算每种地形的阈值范围 + TArray Thresholds; + Thresholds.SetNum(InTerrains.Num()); + + float Accumulated = 0.0f; + for (int32 i = 0; i < InPriority.Num(); i++) + { + Accumulated += static_cast(InPriority[i]) / TotalPriority; + Thresholds[i] = Accumulated; + } + + // 设置柏林噪声参数 + float Frequency = 0.05f; // 可以调整这个值来改变地形的细节程度 + float OffsetX = FMath::RandRange(0.0f, 1000.0f); // 随机偏移以确保每次生成不同的地形 + float OffsetY = FMath::RandRange(0.0f, 1000.0f); + + // 生成地形 + for (int32 Y = 0; Y < InMapSize; Y++) + { + for (int32 X = 0; X < InMapSize; X++) + { + // 计算柏林噪声值 (范围在-1到1之间) + float NoiseValue = FMath::PerlinNoise2D(FVector2D((X + OffsetX) * Frequency, (Y + OffsetY) * Frequency)); + + // 将噪声值映射到0到1范围 + float NormalizedNoise = (NoiseValue + 1.0f) / 2.0f; + + // 根据噪声值选择地形 + for (int32 i = 0; i < Thresholds.Num(); i++) + { + if (NormalizedNoise <= Thresholds[i] || i == Thresholds.Num() - 1) + { + OutTerrains[Y * InMapSize + X] = InTerrains[i]; + break; + } + } + } + } + + return true; +} + + + + + + + + + + +static UPaperTileLayer* GetTileMapLayer( + const FGameplayTag& InTerrainType, + const TMap>& TileMapMeshes +){ + // 将给定的数据绘制到TileMapLayer上 + const TObjectPtr *TileMapMesh = TileMapMeshes.Find(InTerrainType); + if (!TileMapMesh) + { + return nullptr; + } + const TObjectPtr TileMap = TileMapMesh->Get()->TileMap; + if (!TileMap) + { + return nullptr; + } + if (TileMap->TileLayers.Num() == 0) + { + return nullptr; + } + return TileMap->TileLayers[0]; +} + +static UPaperTileSet* GetMapTileSet( + const FGameplayTag& InTerrainType, + TMap>& TileSetConfigs +){ + if (const TObjectPtr *TileSet = TileSetConfigs.Find(InTerrainType)) + { + return TileSet->Get(); + } + return nullptr; +} + +static inline int32 GetTileSetIndex(const bool LeftUp, const bool RightUp, const bool LeftDown, const bool RightDown) +{ + int32 GetTileSetIndex = 0; + GetTileSetIndex += (LeftUp ? 1 : 0) << 3; + GetTileSetIndex += (RightUp ? 1 : 0) << 2; + GetTileSetIndex += (LeftDown ? 1 : 0) << 1; + GetTileSetIndex += (RightDown ? 1 : 0); + return GetTileSetIndex; +} + + + +UTerrainLayerComponent::UTerrainLayerComponent() +{ +} + +void UTerrainLayerComponent::BeginPlay() +{ + Super::BeginPlay(); + SetupTerrainMeshes(); + + TArray Priority; + TArray TerrainData; + TArray TerrainTypes; + TArray FilteredTerrainData; + for (auto TileSetConfig : TileSetConfigs) + { + TerrainTypes.Add(TileSetConfig.Key); + Priority.Add(1); + } + + GenerateTerrain(TerrainTypes, Priority, MapWidth, TerrainData); + + for (auto TerrainType : TerrainTypes) + { + FilteredTerrainData.Init(false, TerrainData.Num()); + for (int32 i = 0; i < TerrainData.Num(); i++) + { + FilteredTerrainData[i] = (TerrainData[i] == TerrainType); + } + SetTerrainData(TerrainType, FilteredTerrainData); + } +} + +void UTerrainLayerComponent::SetTerrainData(const FGameplayTag& InTerrainType, const TArray& TerrainData) +{ + // 将给定的数据绘制到TileMapLayer上 + UPaperTileSet* TileSet = GetMapTileSet(InTerrainType, TileSetConfigs); + UPaperTileLayer *TileMapLayer = GetTileMapLayer(InTerrainType, TerrainMeshes); + if (!TileSet || !TileMapLayer) + { + UE_LOG(LogTemp, Warning, TEXT("UTerrainLayerComponent::SetTerrainData")); + return; + } + if (TerrainData.Num() != MapWidth * MapHeight) + { + UE_LOG(LogTemp, Warning, TEXT("UTerrainLayerComponent::SetTerrainData")); + return; + } + for (int32 i = 0; i < MapHeight - 1; i++) + { + for (int32 j = 0; j < MapWidth - 1; j++) + { + FPaperTileInfo TileInfo; + const int32 CurRow = i * MapWidth; + const int32 NextRow = (i + 1) * MapWidth; + const int32 NeighborIndex = GetTileSetIndex( + TerrainData[CurRow + j], + TerrainData[CurRow + j + 1], + TerrainData[NextRow + j], + TerrainData[NextRow + j + 1] + ); + TileInfo.TileSet = TileSet; + TileInfo.PackedTileIndex = DefaultNeighborDataToIdxMappings[NeighborIndex]; + TileMapLayer->SetCell(j, i, TileInfo); + } + } +} + + +void UTerrainLayerComponent::SetupTerrainMeshes() +{ + if (TileSetConfigs.Num() == 0) + { + return; + } + + TerrainMeshes.Empty(); + USceneComponent *RootScene = GetOwner()->GetRootComponent(); + + int32 Z = 0; + for (const FGameplayTag &TerrainType : SortedTerrainsWithPriority) + { + if (TileSetConfigs.Find(TerrainType) == nullptr) + { + continue; + } + + // 创建一个新的TileMap组件,新的TileMap,新的TileLayer + auto* NewTileMapMesh = NewObject(this); + NewTileMapMesh->RegisterComponent(); + + + UPaperTileMap* NewTileMap = NewObject(NewTileMapMesh); + + NewTileMap->MapWidth = MapWidth; + NewTileMap->MapHeight = MapHeight; + NewTileMap->TileWidth = 128; + NewTileMap->TileHeight = 128; + + + UPaperTileLayer* NewLayer = NewObject(NewTileMap); + NewLayer->LayerName = FText::FromString("TerrainLayer"); + NewLayer->ResizeMap(NewTileMap->MapWidth, NewTileMap->MapHeight); + NewTileMap->TileLayers.Add(NewLayer); + + NewTileMapMesh->SetTileMap(NewTileMap); + NewTileMapMesh->SetRelativeLocation(FVector(-(NewTileMap->TileWidth / 2), -(NewTileMap->TileHeight / 2), Z++)); + NewTileMapMesh->SetRelativeRotation(FRotator(0, 0, -90)); + NewTileMapMesh->AttachToComponent(RootScene, FAttachmentTransformRules::KeepWorldTransform); + TerrainMeshes.Add(TerrainType, NewTileMapMesh); + } +} diff --git a/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp b/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp index b59463b..f938381 100644 --- a/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp +++ b/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp @@ -1,34 +1,33 @@ #include "Level/PaperTerrainMapActor.h" + #include "Level/TerrainGenerator.h" #include "Level/TerrainGeneratorBlueprintLibrary.h" #include "Paper2D/Classes/PaperTileMapComponent.h" -#include "Paper2D/Classes/PaperTileSet.h" #include "Paper2D/Classes/PaperTileMap.h" #include "Paper2D/Classes/PaperTileLayer.h" #include "Engine/Engine.h" #include "UObject/ConstructorHelpers.h" +/* 相邻四格地形信息映射到TileSet的索引 + * 以左上、右上、左下、右下地形块存在为1,不存在为0, 从左到右构建一个4位二进制整数 + * 假设原始TileSet中,0号索引左上、右上、右下为空, 对应二进制整数为0x0010 + * 那么TileSet的这个索引与相邻数据对应关系就是[0] -> 2 + * 也就是DefaultNeighborDataToIdxMappings[2] = 0 + */ +const static int DefaultNeighborDataToIdxMappings[16] = { + 12, 13, 0, 3, 8, 1, 14, 5, 15, 4, 11, 2, 9, 10, 7, 6 +}; + APaperTerrainMapActor::APaperTerrainMapActor() { PrimaryActorTick.bCanEverTick = false; - // 创建根组?? + // 创建根组件 RootComponent = CreateDefaultSubobject(TEXT("RootComponent")); // 创建TileMap组件 TileMapComponent = CreateDefaultSubobject(TEXT("TileMapComponent")); TileMapComponent->SetupAttachment(RootComponent); - - // 创建地形生成?? - TerrainGenerator = CreateDefaultSubobject(TEXT("TerrainGenerator")); - - // 默认参数设置 - bAutoGenerateOnConstruction = true; - bAutoGenerateOnBeginPlay = false; - DefaultTileIndex = 0; - - // 设置默认地形映射 - SetupDefaultTerrainConfig(); } void APaperTerrainMapActor::BeginPlay() @@ -44,7 +43,8 @@ void APaperTerrainMapActor::BeginPlay() void APaperTerrainMapActor::OnConstruction(const FTransform& Transform) { Super::OnConstruction(Transform); - + // 初始化地形生成器 + InitializeGenerator(); // 初始化TileMap InitializeTileMap(); @@ -61,6 +61,11 @@ void APaperTerrainMapActor::GenerateTerrainMap() UE_LOG(LogTemp, Error, TEXT("TerrainGenerator is null!")); return; } + if (!TerrainTileSetConfigs) + { + UE_LOG(LogTemp, Error, TEXT("TerrainTileSetConfigs is null!")); + return; + } // 生成地形数据 GeneratedTerrainData = TerrainGenerator->GenerateMap(MapWidth, MapHeight); @@ -99,16 +104,6 @@ void APaperTerrainMapActor::ClearMap() UE_LOG(LogTemp, Log, TEXT("Map cleared!")); } -UTerrainGenerator* APaperTerrainMapActor::GetTerrainGenerator() const -{ - return TerrainGenerator; -} - -TArray APaperTerrainMapActor::GetGeneratedTerrainData() const -{ - return GeneratedTerrainData; -} - #if WITH_EDITOR void APaperTerrainMapActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { @@ -121,33 +116,31 @@ void APaperTerrainMapActor::PostEditChangeProperty(FPropertyChangedEvent& Proper // 当地图尺寸或Tile大小改变时,重新初始化TileMap if (PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, MapWidth) || PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, MapHeight) || - PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, TileSize)) + PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, TileSize) || + PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, TileSetDataAsset)) + { InitializeTileMap(); - - // 如果已经有生成的数据,重新应?? - if (GeneratedTerrainData.Num() > 0) - { - ApplyTerrainToTileMap(GeneratedTerrainData); - } + GenerateTerrainMap(); } } #endif void APaperTerrainMapActor::InitializeTileMap() { - if (!TileMapComponent) - { - return; - } + if (!TileMapComponent) return; - // 创建或获取TileMap资产 - UPaperTileMap* TileMap = TileMapComponent->TileMap; - if (!TileMap) + if (TileSetDataAsset) { - TileMap = NewObject(this); - TileMapComponent->SetTileMap(TileMap); + TerrainTileSetConfigs = &TileSetDataAsset.Get()->TerrainTileSetConfigs; } + else + { + TerrainTileSetConfigs = nullptr; + } + + // 创建TileMap + UPaperTileMap *TileMap = NewObject(this); // 设置TileMap参数 TileMap->MapWidth = MapWidth; @@ -155,134 +148,128 @@ void APaperTerrainMapActor::InitializeTileMap() TileMap->TileWidth = TileSize; TileMap->TileHeight = TileSize; - // 确保有足够的图层 - if (TileMap->TileLayers.Num() == 0) + // 创建四层layer + for (int i = 0; i < 4; ++i) { UPaperTileLayer* NewLayer = NewObject(TileMap); - NewLayer->LayerName = FText::FromString("TerrainLayer"); + NewLayer->LayerName = FText::FromString( + FString::Format(TEXT("TerrainLayer{0}"), {FString::FromInt(i)}) + ); + NewLayer->ResizeMap(MapWidth, MapHeight); TileMap->TileLayers.Add(NewLayer); } - - // 设置组件大小 - FVector NewScale(TileSize * MapWidth / 100.0f, 1.0f, TileSize * MapHeight / 100.0f); - TileMapComponent->SetRelativeScale3D(NewScale); - + + TileMapComponent->SetTileMap(TileMap); UE_LOG(LogTemp, Log, TEXT("TileMap initialized: %dx%d, TileSize: %d"), MapWidth, MapHeight, TileSize); } -void APaperTerrainMapActor::ApplyTerrainToTileMap(const TArray& TerrainData) +void APaperTerrainMapActor::InitializeGenerator() +{ + // 创建地形生成器 + if (GeneratorClass.Get()) + { + TerrainGenerator = NewObject(this, GeneratorClass.Get()); + UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(TerrainGenerator); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::APaperTerrainMapActor, GeneratorClass is nullptr")); + } +} + +void APaperTerrainMapActor::ApplyTerrainToTileMap(const TArray& TerrainData) const { if (!TileMapComponent || !TileMapComponent->TileMap || TerrainData.Num() != MapWidth * MapHeight) { UE_LOG(LogTemp, Error, TEXT("Cannot apply terrain data to TileMap!")); return; } - if (TileMapComponent->TileMap->TileLayers.Num() == 0) { UE_LOG(LogTemp, Error, TEXT("No tile layers in TileMap!")); return; } - // 应用地形数据到TileMap - 创建新的TileMap并设置所有单元格 - UPaperTileMap* NewTileMap = NewObject(this); - NewTileMap->MapWidth = MapWidth; - NewTileMap->MapHeight = MapHeight; - NewTileMap->TileWidth = TileSize; - NewTileMap->TileHeight = TileSize; - - // 创建地形图层 - UPaperTileLayer* TerrainLayer = NewObject(NewTileMap); - TerrainLayer->LayerName = FText::FromString("TerrainLayer"); - NewTileMap->TileLayers.Add(TerrainLayer); - - // 设置图层单元??- 使用SetCell方法 - // 首先调整图层尺寸 - TerrainLayer->ResizeMap(MapWidth, MapHeight); + FGameplayTag TerrainTags[4]; + const int32 LimitedWidth = MapWidth - 1; + const int32 LimitedHeight = MapHeight - 1; - for (int32 Y = 0; Y < MapHeight; Y++) + for (int32 Y = 0; Y < LimitedHeight; Y++) { - for (int32 X = 0; X < MapWidth; X++) + for (int32 X = 0; X < LimitedWidth; X++) { - int32 Index = Y * MapWidth + X; - if (TerrainData.IsValidIndex(Index)) - { - FGameplayTag TerrainTag = TerrainData[Index]; - int32 TileIndex = GetTileIndexForTerrain(TerrainTag); - - // 创建Tile信息并使用SetCell方法 - FPaperTileInfo TileInfo; - TileInfo.TileSet = TileSet; - TileInfo.PackedTileIndex = TileIndex; - TerrainLayer->SetCell(X, Y, TileInfo); - } + TerrainTags[0] = TerrainData[Y * MapWidth + X]; + TerrainTags[1] = TerrainData[Y * MapWidth + X + 1]; + TerrainTags[2] = TerrainData[(Y + 1) * MapWidth + X]; + TerrainTags[3] = TerrainData[(Y + 1) * MapWidth + X + 1]; + DrawTile(X, Y, TerrainTags); } } - - // 设置新的TileMap - TileMapComponent->SetTileMap(NewTileMap); - UE_LOG(LogTemp, Log, TEXT("Terrain data applied to TileMap!")); } -int32 APaperTerrainMapActor::GetTileIndexForTerrain(const FGameplayTag& TerrainTag) const +static inline int32 GetNeighborInfo(const FGameplayTag& TargetTag, FGameplayTag TerrainTags[4]) { - if (!TerrainTag.IsValid()) - { - return DefaultTileIndex; - } + int32 NeighborInfo = 0; + NeighborInfo += (TargetTag == TerrainTags[0] ? 1 : 0) << 3; + NeighborInfo += (TargetTag == TerrainTags[1] ? 1 : 0) << 2; + NeighborInfo += (TargetTag == TerrainTags[2] ? 1 : 0) << 1; + NeighborInfo += (TargetTag == TerrainTags[3] ? 1 : 0); + return NeighborInfo; +} - // 查找地形映射 - for (const FTerrainTileMapping& Mapping : TerrainTileMappings) +static inline void GetSortedLayerDrawingInfo(FGameplayTag **LayerTerrainTags, int32 *LayerNeighborInfo, FGameplayTag TerrainTags[4]) +{ + // 返回0-3层每层应该画哪种地形格子,以及这个格子的哪种形状,可能需要排序 + LayerTerrainTags[0] = &TerrainTags[0]; + LayerTerrainTags[1] = &TerrainTags[1]; + LayerTerrainTags[2] = &TerrainTags[2]; + LayerTerrainTags[3] = &TerrainTags[3]; + LayerNeighborInfo[0] = GetNeighborInfo(TerrainTags[0], TerrainTags); + LayerNeighborInfo[1] = GetNeighborInfo(TerrainTags[1], TerrainTags); + LayerNeighborInfo[2] = GetNeighborInfo(TerrainTags[2], TerrainTags); + LayerNeighborInfo[3] = GetNeighborInfo(TerrainTags[3], TerrainTags); +} + +void APaperTerrainMapActor::DrawTile(const int32 X, const int32 Y, FGameplayTag TerrainTags[4])const +{ + int32 LayerNeighborInfo[4]; + FGameplayTag *LayerTerrainTags[4]; + UPaperTileMap* TileMap = TileMapComponent->TileMap; + GetSortedLayerDrawingInfo(LayerTerrainTags, LayerNeighborInfo, TerrainTags); + + for (int32 Index = 0; Index < 4; ++Index) { - if (Mapping.TerrainTag == TerrainTag) + int32 TileIndex = 0; + FPaperTileInfo TileInfo; + + const FTerrainTileSetConfig *TileSetConfig = TerrainTileSetConfigs->Find(*LayerTerrainTags[Index]); + if (!TileSetConfig) { - return Mapping.TileIndex; + UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::DrawTile TileSetConfig not found: %s"), *LayerTerrainTags[Index]->GetTagName().ToString()); + continue; } - } - - // 如果没有找到映射,返回默认Tile - return DefaultTileIndex; -} - -void APaperTerrainMapActor::SetupDefaultTerrainConfig() -{ - // 设置默认地形映射 - TerrainTileMappings.Empty(); - - // 森林 -> Tile 1 - FTerrainTileMapping ForestMapping; - ForestMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Forest")); - ForestMapping.TileIndex = 1; - TerrainTileMappings.Add(ForestMapping); - - // 草地 -> Tile 2 - FTerrainTileMapping GrasslandMapping; - GrasslandMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Grassland")); - GrasslandMapping.TileIndex = 2; - TerrainTileMappings.Add(GrasslandMapping); - - // 水体 -> Tile 3 - FTerrainTileMapping WaterMapping; - WaterMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water")); - WaterMapping.TileIndex = 3; - TerrainTileMappings.Add(WaterMapping); - - // 沼泽 -> Tile 4 - FTerrainTileMapping SwampMapping; - SwampMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Swamp")); - SwampMapping.TileIndex = 4; - TerrainTileMappings.Add(SwampMapping); - - // 土地 -> Tile 5 - FTerrainTileMapping LandMapping; - LandMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Land")); - LandMapping.TileIndex = 5; - TerrainTileMappings.Add(LandMapping); - - // 设置地形生成器的默认配置 - if (TerrainGenerator) - { - UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(TerrainGenerator); + const int32 NeighborInfo = LayerNeighborInfo[Index]; + if (TileSetConfig->bNeedOverrideMappings) + { + if (NeighborInfo < TileSetConfig->NeighborDataToIdxMappings.Num()) + { + TileIndex = TileSetConfig->NeighborDataToIdxMappings[NeighborInfo]; + } + else + { + UE_LOG(LogTemp, Warning, TEXT("APaperTerrainMapActor::DrawTile Neighbor Data not find: %s, %d"), + *LayerTerrainTags[Index]->GetTagName().ToString(), NeighborInfo + ) + } + } + else + { + TileIndex = DefaultNeighborDataToIdxMappings[NeighborInfo]; + } + UPaperTileLayer* TerrainLayer = TileMap->TileLayers[Index]; + TileInfo.TileSet = TileSetConfig->TileSet; + TileInfo.PackedTileIndex = TileIndex; + TerrainLayer->SetCell(X, Y, TileInfo); } } diff --git a/Source/BusyRabbit/Private/Level/TerrainGenerator.cpp b/Source/BusyRabbit/Private/Level/TerrainGenerator.cpp index b012b56..51d02c2 100644 --- a/Source/BusyRabbit/Private/Level/TerrainGenerator.cpp +++ b/Source/BusyRabbit/Private/Level/TerrainGenerator.cpp @@ -7,196 +7,20 @@ UTerrainGenerator::UTerrainGenerator() NextHandle = 0; } -int32 UTerrainGenerator::AddTerrain(const FGameplayTag& TerrainTag) + +bool UTerrainGenerator::GenerateWithNodes(TArray& Map, int32 Width, int32 Height, const TArray& ValidLeafNodes) { - FTerrainNodeInfo NewNode; - NewNode.TerrainTag = TerrainTag; - NewNode.Probability = 1.0f; - NewNode.Weight = 1.0f; - NewNode.ParentHandle = -1; - NewNode.bIsLeafNode = true; - - int32 Handle = NextHandle++; - TerrainNodes.Add(Handle, NewNode); - - return Handle; -} - -void UTerrainGenerator::SetWeight(int32 Handle, float Weight) -{ - if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)) - { - Node->Weight = FMath::Max(0.0f, Weight); - } -} - -void UTerrainGenerator::SetProbability(int32 Handle, float Probability) -{ - if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)) - { - Node->Probability = FMath::Clamp(Probability, 0.0f, 1.0f); - } -} - -void UTerrainGenerator::SetExclusive(const TArray& Handles){ - if (Handles.Num() <= 1) return; - ExclusiveGroups.Add(TSet(Handles)); -} - -void UTerrainGenerator::BindChildTerrain(int32 ParentHandle, const TArray& ChildHandles) -{ - if (FTerrainNodeInfo* ParentNode = TerrainNodes.Find(ParentHandle)) - { - // 设置父节点为非叶子节点 - ParentNode->bIsLeafNode = false; - - // 添加子节点 - ParentNode->ChildHandles = ChildHandles; - - // 设置子节点的父节点 - for (int32 ChildHandle : ChildHandles) - { - if (FTerrainNodeInfo* ChildNode = TerrainNodes.Find(ChildHandle)) - { - ChildNode->ParentHandle = ParentHandle; - } - } - } -} - -TArray UTerrainGenerator::GenerateMap(int32 Width, int32 Height) -{ - TArray Map; - Map.SetNum(Width * Height); - // 获取有效的叶子节点 - TArray ValidLeafNodes = CalcValidLeafNodes(); - - if (ValidLeafNodes.Num() == 0) - { - UE_LOG(LogTemp, Error, TEXT("No valid terrain nodes to generate map!")); - return Map; - } - // 使用区域生长算法生成基础地图 RegionGrowing(Map, Width, Height, ValidLeafNodes); // 应用柏林噪声进行平滑处理 ApplyPerlinNoiseSmoothing(Map, Width, Height); - return Map; -} - -TMap UTerrainGenerator::GetAllTerrainNodes() const -{ - return TerrainNodes; -} - -void UTerrainGenerator::GetPreCheckVisibleHandles(TSet& VisibleHandles)const{ - bool bShouldAppear; - const FTerrainNodeInfo* Node; - int32 Handle, TerrainNodeCount; - TArray AppearanceCheckResult; - - TerrainNodeCount = TerrainNodes.Num(); - AppearanceCheckResult.Init(true, TerrainNodeCount); - - for (const auto& Pair : TerrainNodes) { - Handle = Pair.Key; - Node = &Pair.Value; - - if (AppearanceCheckResult[Handle]) { - bShouldAppear = FMath::FRand() < Node->Probability; - AppearanceCheckResult[Handle] = bShouldAppear; - } - else { - bShouldAppear = false; - } - - if (!bShouldAppear) { - for (auto ChildHandle : Pair.Value.ChildHandles) { - AppearanceCheckResult[ChildHandle] = false; - } - } - } - - for (int i = 0; i < TerrainNodeCount; ++i) { - if (AppearanceCheckResult[i]) { - VisibleHandles.Add(i); - } - } -} - -bool UTerrainGenerator::ShouldNodeAppear(int32 Handle) const -{ - const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle); - if (!Node) - { - return false; - } - - // 检查概率 - if (FMath::FRand() > Node->Probability) - { - return false; - } - - // 递归检查父节点 - if (Node->ParentHandle != -1) - { - return ShouldNodeAppear(Node->ParentHandle); - } - return true; } -bool UTerrainGenerator::CheckExclusive(int32 Handle, const TSet& AppearingNodes) const { - //for (const TArray& Group : ExclusiveGroups) - //{ - // if (Group.Contains(Handle)) - // { - // // 检查互斥组中是否有其他节点已经出现 - // for (int32 OtherHandle : Group) - // { - // if (OtherHandle != Handle && AppearingNodes.Contains(OtherHandle)) - // { - // return false; - // } - // } - // } - //} - return true; -} - -TArray UTerrainGenerator::CalcValidLeafNodes()const{ - TSet ValidNodes; - GetPreCheckVisibleHandles(ValidNodes); - - // 检查互斥关系 - TSet FinalNodes; - for (int32 Handle : ValidNodes){ - if (FinalNodes.Find(Handle)) continue; - if (CheckExclusive(Handle, ValidNodes)) - { - FinalNodes.Add(Handle); - } - } - - // 只保留叶子节点 - TArray LeafNodes; - for (int32 Handle : FinalNodes) - { - const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle); - if (Node && Node->bIsLeafNode) - { - LeafNodes.Add(Handle); - } - } - - return LeafNodes; -} - void UTerrainGenerator::RegionGrowing(TArray& Map, int32 Width, int32 Height, - const TArray& ValidLeafNodes) const + const TArray& ValidLeafNodes) const { if (ValidLeafNodes.Num() == 0) { @@ -393,10 +217,4 @@ void UTerrainGenerator::ApplyPerlinNoiseSmoothing(TArray& Map, int } } -FGameplayTag UTerrainGenerator::GetFinalTerrainTag(int32 Handle) const{ - const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle); - if (Node){ - return Node->TerrainTag; - } - return FGameplayTag(); -} + diff --git a/Source/BusyRabbit/Private/Level/TerrainGeneratorBlueprintLibrary.cpp b/Source/BusyRabbit/Private/Level/TerrainGeneratorBlueprintLibrary.cpp index c6d8057..04d0d79 100644 --- a/Source/BusyRabbit/Private/Level/TerrainGeneratorBlueprintLibrary.cpp +++ b/Source/BusyRabbit/Private/Level/TerrainGeneratorBlueprintLibrary.cpp @@ -8,7 +8,7 @@ UTerrainGenerator* UTerrainGeneratorBlueprintLibrary::CreateTerrainGenerator() return NewObject(); } -void UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(UTerrainGenerator* Generator) +void UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(UTerrainGeneratorBase* Generator) { if (!Generator) { diff --git a/Source/BusyRabbit/Private/Role/BusyRoleMovement.cpp b/Source/BusyRabbit/Private/Role/BusyRoleMovement.cpp index 613f943..5dc2cbe 100644 --- a/Source/BusyRabbit/Private/Role/BusyRoleMovement.cpp +++ b/Source/BusyRabbit/Private/Role/BusyRoleMovement.cpp @@ -15,8 +15,11 @@ void UBusyRoleMovement::BeginPlay(){ } -void UBusyRoleMovement::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction){ +void UBusyRoleMovement::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + const AActor* Owner = GetOwner(); + if (!Owner) return; this->ReceiveComponentTick(DeltaTime); } diff --git a/Source/BusyRabbit/Public/Level/Actor/BusyPawnBase.h b/Source/BusyRabbit/Public/Level/Actor/BusyPawnBase.h new file mode 100644 index 0000000..d04d6fc --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Actor/BusyPawnBase.h @@ -0,0 +1,61 @@ +#pragma once +#include "LuaPawn.h" +#include "Level/Actor/Components/BusyPawnMovement.h" +#include "BusyPawnBase.generated.h" + + +class USphereComponent; +class USpineBoneFollowerComponent; +class USpineSkeletonRendererComponent; +class USpineSkeletonAnimationComponent; + + +UCLASS() +class ABusyPawnBase : public ALuaPawn, public IBusyMovable +{ + GENERATED_BODY() +public: + ABusyPawnBase(); + + virtual void BeginPlay()override; + + virtual void UpdateMoveDirection_Implementation(const FVector2D& InDirection) override; + + virtual float GetSpeed_Implementation()const override; + +protected: + UPROPERTY(EditDefaultsOnly) + TObjectPtr RootScene; //场景根组件 + + /*-----------------------------碰撞相关组件-----------------------------*/ + UPROPERTY(EditDefaultsOnly) + TObjectPtr SphereComponent; + /*-------------------------------------------------------------------*/ + + + /*----------------------------spine相关组件----------------------------*/ + UPROPERTY(EditDefaultsOnly) + TObjectPtr SpineRoot; + + UPROPERTY(EditDefaultsOnly) + TObjectPtr SpineRenderComponent; + + UPROPERTY(EditAnywhere, BlueprintReadOnly) + TObjectPtr SpineAnimationComponent; + /*-------------------------------------------------------------------*/ + + + /*-------------------------------移动组件------------------------------*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly) + TObjectPtr MovementComponent; + /*-------------------------------------------------------------------*/ + + +protected: + UPROPERTY(EditAnywhere) + FString DefaultSkinName; + + UPROPERTY(EditAnywhere) + FString DefaultAnimationName; + +}; diff --git a/Source/BusyRabbit/Public/Level/Actor/BusyPlayerRole.h b/Source/BusyRabbit/Public/Level/Actor/BusyPlayerRole.h new file mode 100644 index 0000000..54c5230 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Actor/BusyPlayerRole.h @@ -0,0 +1,32 @@ +#pragma once +#include "BusyPawnBase.h" +#include "BusyPlayerRole.generated.h" + +UCLASS() +class ABusyPlayerRole : public ABusyPawnBase +{ + GENERATED_BODY() +public: + + ABusyPlayerRole(); + +protected: + /*--------------------相机相关--------------------------*/ + + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) + TObjectPtr SpringArmComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) + TObjectPtr CameraComponent; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) + TObjectPtr Movement; // 移动组件 + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) + TObjectPtr RoleAbility; // 技能组件 + + UPROPERTY(BlueprintReadWrite) + TObjectPtr RoleAnimation; + +}; diff --git a/Source/BusyRabbit/Public/Level/Actor/Components/BusyPawnMovement.h b/Source/BusyRabbit/Public/Level/Actor/Components/BusyPawnMovement.h new file mode 100644 index 0000000..cfa8f1d --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Actor/Components/BusyPawnMovement.h @@ -0,0 +1,47 @@ +#pragma once +#include "LuaActorComponent.h" +#include "BusyPawnMovement.generated.h" + +UINTERFACE(MinimalAPI, Blueprintable) +class UBusyMovable : public UInterface +{ + GENERATED_BODY() +}; + +class IBusyMovable +{ + GENERATED_BODY() +public: + + UFUNCTION(BlueprintNativeEvent) + float GetSpeed() const; + + UFUNCTION(BlueprintNativeEvent, Category = "Movement") + void UpdateMoveDirection(const FVector2D &InDirection); + +}; + + +UCLASS() +class UBusyPawnMovement : public ULuaActorComponent +{ + GENERATED_BODY() +public: + UBusyPawnMovement(); + +public: + UFUNCTION(BlueprintCallable) + void MoveTo(const FVector2D& Target); + + UFUNCTION(BlueprintCallable) + FVector2D GetMoveDirection()const; + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)override; +public: + UPROPERTY(EditAnywhere, BlueprintReadOnly) + float MoveSpeed = 400; + +protected: + UPROPERTY(EditAnywhere, BlueprintReadOnly) + FVector2D MoveTargetLocation; +}; diff --git a/Source/BusyRabbit/Public/Level/Generator/MitchellBestCandidate.h b/Source/BusyRabbit/Public/Level/Generator/MitchellBestCandidate.h new file mode 100644 index 0000000..304c239 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Generator/MitchellBestCandidate.h @@ -0,0 +1,61 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "MitchellBestCandidate.generated.h" + +/** + * @brief 米切尔最佳候选算法实现类 + * + * 该算法用于生成高质量、均匀分布的随机点集,常用于程序化内容生成。 + * 通过为每个点生成多个候选点并选择距离现有点集最远的点,确保点集分布均匀。 + */ +UCLASS(BlueprintType, Blueprintable) +class BUSYRABBIT_API UMitchellBestCandidate : public UObject +{ + GENERATED_BODY() + +public: + UMitchellBestCandidate(); + + /** + * @brief 生成米切尔最佳候选点集 + * + * 使用米切尔最佳候选算法生成均匀分布的点集。算法为每个点生成多个候选点, + * 然后选择距离现有点集最远的候选点,确保点集分布均匀。 + * + * @param NumPoints 要生成的点数 + * @param Width 生成区域的宽度 + * @param Height 生成区域的高度 + * @param NumCandidates 每个点生成的候选点数(默认为10,值越大点集越均匀但性能开销越大) + * @return 生成的均匀分布点集 + */ + UFUNCTION(BlueprintCallable, Category = "Mitchell Best Candidate") + TArray GeneratePoints(int32 NumPoints, float Width, float Height, int32 NumCandidates = 10)const; + + /** + * @brief 计算两点之间的欧几里得距离 + * + * @param A 第一个点 + * @param B 第二个点 + * @return 两点之间的欧几里得距离 + */ + UFUNCTION(BlueprintCallable, Category = "Mitchell Best Candidate") + static float Distance(const FVector2D& A, const FVector2D& B); + + /** + * @brief 计算点到点集的最小距离 + * + * 计算给定点到点集中所有点的最小距离,用于评估候选点的质量。 + * + * @param Point 要评估的点 + * @param PointSet 点集 + * @return 点到点集的最小距离 + */ + UFUNCTION(BlueprintCallable, Category = "Mitchell Best Candidate") + static float MinDistanceToSet(const FVector2D& Point, const TArray& PointSet); + +private: + /** 随机数生成器,用于生成随机点 */ + FRandomStream RandomStream; +}; diff --git a/Source/BusyRabbit/Public/Level/Generator/TerrainGeneratorBase.h b/Source/BusyRabbit/Public/Level/Generator/TerrainGeneratorBase.h new file mode 100644 index 0000000..1feefbf --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Generator/TerrainGeneratorBase.h @@ -0,0 +1,89 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "TerrainGeneratorBase.generated.h" + +USTRUCT(BlueprintType) +struct FTerrainNodeInfo +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FGameplayTag TerrainTag; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float Probability; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + float Weight; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + int32 ParentHandle; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray ChildHandles; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bIsLeafNode; + + FTerrainNodeInfo() + : Probability(1.0f) + , Weight(1.0f) + , ParentHandle(-1) + , bIsLeafNode(false) + { + } +}; + + +UCLASS(BlueprintType, Blueprintable) +class BUSYRABBIT_API UTerrainGeneratorBase : public UObject +{ + GENERATED_BODY() + +public: + UTerrainGeneratorBase(); + + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + int32 AddTerrain(const FGameplayTag& TerrainTag); + + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + void SetWeight(int32 Handle, float Weight); + + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + void SetProbability(int32 Handle, float Probability); + + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + void SetExclusive(const TArray& Handles); + + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + void BindChildTerrain(int32 ParentHandle, const TArray& ChildHandles); + + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + TArray GenerateMap(int32 Width, int32 Height); + +public: // get + FGameplayTag GetFinalTerrainTag(int32 Handle) const; + + TArray GetValidLeafNodes()const; + + void GetPreCheckVisibleHandles(TSet& VisibleHandles)const; + + + +public: + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + TMap GetAllTerrainNodes() const {return TerrainNodes;}; + +public: // 需要子类重写的函数 + + + virtual bool GenerateWithNodes(TArray& Map, int32 Width, int32 Height, const TArray& ValidLeafNodes); + +protected: + int32 NextHandle; + TArray> ExclusiveGroups; + TMap TerrainNodes; + +}; diff --git a/Source/BusyRabbit/Public/Level/Generator/VoronoiDiagram.h b/Source/BusyRabbit/Public/Level/Generator/VoronoiDiagram.h new file mode 100644 index 0000000..b7672d4 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Generator/VoronoiDiagram.h @@ -0,0 +1,139 @@ +#pragma once + +#include "CoreMinimal.h" +#include "VoronoiDiagram.generated.h" + +/** + * @brief 泰森多边形单元结构 + * + * 表示泰森多边形图中的一个单元,包含生成点、顶点和相邻单元信息。 + */ +USTRUCT(BlueprintType) +struct FVoronoiCell +{ + GENERATED_BODY() + + /** 泰森多边形单元的生成点(站点) */ + UPROPERTY(BlueprintReadOnly) + FVector2D Site; + + /** 泰森多边形单元的顶点列表,按顺时针或逆时针顺序排列 */ + UPROPERTY(BlueprintReadOnly) + TArray Vertices; + + /** 相邻泰森多边形单元的索引列表 */ + UPROPERTY(BlueprintReadOnly) + TArray NeighborCells; + + /** 默认构造函数 */ + FVoronoiCell() + : Site(FVector2D::ZeroVector) + { + } +}; + +/** + * @brief 泰森多边形图生成器 + * + * 用于生成和管理泰森多边形图(Voronoi Diagram),将平面划分为多个区域, + * 每个区域包含距离其生成点最近的所有点。 + */ +UCLASS(BlueprintType, Blueprintable) +class BUSYRABBIT_API UVoronoiDiagram : public UObject +{ + GENERATED_BODY() + +public: + UVoronoiDiagram(); + + /** + * @brief 生成完整的泰森多边形图 + * + * 根据给定的生成点集创建泰森多边形图,将平面划分为多个区域。 + * 每个区域包含距离对应生成点最近的所有点。 + * + * @param Sites 生成点集(站点) + * @param Width 生成区域的宽度 + * @param Height 生成区域的高度 + * @return 生成的泰森多边形单元数组 + */ + UFUNCTION(BlueprintCallable, Category = "Voronoi Diagram") + TArray GenerateDiagram(const TArray& Sites, float Width, float Height); + + /** + * @brief 查找点所在的泰森多边形单元索引 + * + * 通过计算点到所有生成点的距离,找到最近的生成点对应的单元索引。 + * + * @param Point 要查询的点坐标 + * @param Sites 生成点集 + * @return 单元索引(从0开始),如果未找到返回-1 + */ + UFUNCTION(BlueprintCallable, Category = "Voronoi Diagram") + static int32 FindCellIndex(const FVector2D& Point, const TArray& Sites); + + /** + * @brief 将泰森多边形图转换为二维网格 + * + * 将连续的泰森多边形图离散化为网格,每个网格单元格包含对应的生成点索引。 + * + * @param Sites 生成点集 + * @param Width 网格的宽度(单元格数量) + * @param Height 网格的高度(单元格数量) + * @param RegionWidth 实际区域的宽度 + * @param RegionHeight 实际区域的高度 + * @return 网格数据数组,每个元素是对应的生成点索引 + */ + UFUNCTION(BlueprintCallable, Category = "Voronoi Diagram") + static TArray ConvertToGrid(const TArray& Sites, int32 Width, int32 Height, float RegionWidth, float RegionHeight); + +private: + /** + * @brief 计算两个点的垂直平分线 + * + * 计算两点连线的垂直平分线,用于泰森多边形边界的生成。 + * + * @param A 第一个点 + * @param B 第二个点 + * @param OutMidpoint 输出的中点坐标 + * @param OutDirection 输出的垂直方向向量(单位向量) + * @return 是否成功计算(两点不能重合) + */ + static bool CalculatePerpendicularBisector(const FVector2D& A, const FVector2D& B, FVector2D& OutMidpoint, FVector2D& OutDirection); + + /** + * @brief 计算两条直线的交点 + * + * 计算两条直线的交点,用于确定泰森多边形顶点。 + * + * @param P1 第一条直线的起点 + * @param D1 第一条直线的方向向量 + * @param P2 第二条直线的起点 + * @param D2 第二条直线的方向向量 + * @param OutIntersection 输出的交点坐标 + * @return 是否成功计算(直线不能平行) + */ + static bool CalculateLineIntersection(const FVector2D& P1, const FVector2D& D1, const FVector2D& P2, const FVector2D& D2, FVector2D& OutIntersection); + + /** + * @brief 检查点是否在边界框内 + * + * @param Point 要检查的点 + * @param Width 边界框宽度 + * @param Height 边界框高度 + * @return 点是否在边界框内 + */ + static bool IsPointInBounds(const FVector2D& Point, float Width, float Height); + + /** + * @brief 裁剪多边形到边界 + * + * 移除多边形中超出边界的顶点,确保多边形在指定边界内。 + * + * @param Polygon 要裁剪的多边形顶点列表 + * @param Width 边界宽度 + * @param Height 边界高度 + * @return 裁剪后的多边形顶点列表 + */ + static TArray ClipPolygonToBounds(const TArray& Polygon, float Width, float Height); +}; diff --git a/Source/BusyRabbit/Public/Level/Generator/VoronoiTerrainGenerator.h b/Source/BusyRabbit/Public/Level/Generator/VoronoiTerrainGenerator.h new file mode 100644 index 0000000..5f89d13 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Generator/VoronoiTerrainGenerator.h @@ -0,0 +1,78 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "Level/Generator/MitchellBestCandidate.h" +#include "Level/Generator/VoronoiDiagram.h" +#include "Level/Generator/TerrainGeneratorBase.h" +#include "VoronoiTerrainGenerator.generated.h" + + +/** + * @brief 泰森多边形地形生成器 + * + * 结合米切尔最佳候选算法和泰森多边形算法的高级地形生成器。 + * 使用米切尔算法生成均匀分布的点集,然后使用泰森多边形划分区域, + * 最后根据权重分配地形类型到各个区域。 + */ +UCLASS(BlueprintType, Blueprintable) +class BUSYRABBIT_API UVoronoiTerrainGenerator : public UTerrainGeneratorBase +{ + GENERATED_BODY() + +public: + UVoronoiTerrainGenerator(); + + + /** + * @brief 设置泰森多边形区域数量 + * + * 控制地图被划分的泰森多边形区域数量,影响地形的粒度。 + * + * @param Count 区域数量(至少为1) + */ + UFUNCTION(BlueprintCallable, Category = "Voronoi Terrain Generator") + void SetVoronoiRegionCount(int32 Count); + + /** + * @brief 设置米切尔候选点数 + * + * 控制米切尔算法中每个点生成的候选点数,影响点集的质量。 + * + * @param Count 候选点数(至少为1) + */ + UFUNCTION(BlueprintCallable, Category = "Voronoi Terrain Generator") + void SetMitchellCandidateCount(int32 Count); + + virtual bool GenerateWithNodes(TArray& Map, int32 Width, int32 Height, const TArray& ValidLeafNodes)override; + +private: + /** 泰森多边形区域数量参数 */ + int32 VoronoiRegionCount; + + /** 米切尔候选点数参数 */ + int32 MitchellCandidateCount; + + /** 米切尔最佳候选算法实例 */ + UPROPERTY() + UMitchellBestCandidate* MitchellGenerator; + + /** 泰森多边形算法实例 */ + UPROPERTY() + UVoronoiDiagram* VoronoiGenerator; + + + + /** + * @brief 使用泰森多边形算法生成地图 + * + * 核心生成算法,结合米切尔最佳候选和泰森多边形技术。 + * + * @param Map 输出的地图数组 + * @param Width 地图宽度 + * @param Height 地图高度 + * @param ValidLeafNodes 有效的叶子节点列表 + */ + void GenerateWithVoronoi(TArray& Map, int32 Width, int32 Height, + const TArray& ValidLeafNodes) const; +}; diff --git a/Source/BusyRabbit/Public/Level/LevelPlayerController.h b/Source/BusyRabbit/Public/Level/LevelPlayerController.h new file mode 100644 index 0000000..b1f4f75 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/LevelPlayerController.h @@ -0,0 +1,41 @@ +#pragma once + +#include "LuaPlayerController.h" +#include "LevelPlayerController.generated.h" + +class ABusyPlayerRole; + +UCLASS() +class ALevelPlayerController : public ALuaPlayerController +{ + GENERATED_BODY() +public: + ALevelPlayerController(); + +public: + virtual void BeginPlay() override; + + +public: + UFUNCTION(BlueprintCallable, Category = "Controller") + bool GetCursorPosition(FVector2D& Position) const; + + UFUNCTION(BlueprintCallable, Category = "Controller") + void GetCursorHitResult(TArray& Results) const; + + UFUNCTION(BlueprintCallable, Category = "Controller") + void GetControlledRole() const; + + UFUNCTION(BlueprintCallable, Category = "Controller") + void SwitchControlledRole(ABusyPlayerRole* Target); + + + +public: // 输入相关 + UPROPERTY(EditDefaultsOnly, Category = "Input") + TObjectPtr InputMapping; + + UPROPERTY(EditDefaultsOnly, Category = "Input") + TObjectPtr TouchAction; + +}; diff --git a/Source/BusyRabbit/Public/Level/LevelPlayerState.h b/Source/BusyRabbit/Public/Level/LevelPlayerState.h new file mode 100644 index 0000000..835edb6 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/LevelPlayerState.h @@ -0,0 +1,40 @@ +#pragma once + +#include "LuaPlayerState.h" +#include "LevelPlayerState.generated.h" + +class ABusyPlayerRole; + + +DECLARE_LOG_CATEGORY_EXTERN(LogLevelPlayerState, Log, All); + +UCLASS() +class ALevelPlayerState : public ALuaPlayerState +{ + GENERATED_BODY() +public: + virtual void BeginPlay() override; + + + +public: + UFUNCTION(BlueprintCallable) + ABusyPlayerRole* GetControlledRole() const; + + + +protected: + virtual FVector2D GetSpawnLocation()const; + +public: + UPROPERTY(EditAnywhere) + TArray> RoleClasses; + +protected: + UPROPERTY() + int ControlledRoleIndex = -1; + + + UPROPERTY() + TArray Roles; +}; diff --git a/Source/BusyRabbit/Public/Level/Map/ClimateLayerComponent.h b/Source/BusyRabbit/Public/Level/Map/ClimateLayerComponent.h new file mode 100644 index 0000000..d30962e --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Map/ClimateLayerComponent.h @@ -0,0 +1,7 @@ +#pragma once + +class ClimateLayerComponent +{ +public: + +}; diff --git a/Source/BusyRabbit/Public/Level/Map/CreatureLayerComponent.h b/Source/BusyRabbit/Public/Level/Map/CreatureLayerComponent.h new file mode 100644 index 0000000..e824902 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Map/CreatureLayerComponent.h @@ -0,0 +1,7 @@ +#pragma once + +class CreatureLayerComponent +{ +public: + +}; diff --git a/Source/BusyRabbit/Public/Level/Map/DecorationLayerComponent.h b/Source/BusyRabbit/Public/Level/Map/DecorationLayerComponent.h new file mode 100644 index 0000000..bff6d16 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Map/DecorationLayerComponent.h @@ -0,0 +1,7 @@ +#pragma once + +class DecorationLayerComponent +{ +public: + +}; diff --git a/Source/BusyRabbit/Public/Level/Map/GameMapActor.h b/Source/BusyRabbit/Public/Level/Map/GameMapActor.h new file mode 100644 index 0000000..76cd1a8 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Map/GameMapActor.h @@ -0,0 +1,25 @@ +#pragma once +#include "Level/Map/TerrainLayerComponent.h" +#include "GameMapActor.generated.h" + +UCLASS(Blueprintable, Blueprintable) +class AGameMapActor : public AActor +{ + GENERATED_BODY() +public: + AGameMapActor(); + +public: + virtual void BeginPlay() override; + + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + +protected: + UPROPERTY(EditDefaultsOnly) + TObjectPtr TerrainLayer; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) + TObjectPtr SceneComp; + +}; + diff --git a/Source/BusyRabbit/Public/Level/Map/LightingLayerComponent.h b/Source/BusyRabbit/Public/Level/Map/LightingLayerComponent.h new file mode 100644 index 0000000..dc825ec --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Map/LightingLayerComponent.h @@ -0,0 +1,7 @@ +#pragma once + +class LightingLayerComponent +{ +public: + +}; diff --git a/Source/BusyRabbit/Public/Level/Map/PlacementLayerComponent.h b/Source/BusyRabbit/Public/Level/Map/PlacementLayerComponent.h new file mode 100644 index 0000000..42ad5de --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Map/PlacementLayerComponent.h @@ -0,0 +1,7 @@ +#pragma once + +class PlacementLayerComponent +{ +public: + +}; diff --git a/Source/BusyRabbit/Public/Level/Map/TerrainLayerComponent.h b/Source/BusyRabbit/Public/Level/Map/TerrainLayerComponent.h new file mode 100644 index 0000000..c0c4c10 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/Map/TerrainLayerComponent.h @@ -0,0 +1,66 @@ +#pragma once + +#include "GameplayTagContainer.h" +#include "Components/ActorComponent.h" +#include "Paper2D/Classes/PaperTileMapComponent.h" +#include "TerrainLayerComponent.generated.h" + + +USTRUCT(BlueprintType) +struct FTerrainTileSetConfig{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadOnly, DisplayName="该地形对应的TileSet") + TObjectPtr TileSet; + + + UPROPERTY(EditAnywhere, BlueprintReadOnly, DisplayName="是否需要重载周围数据信息到TileSet的索引映射") + bool bNeedOverrideMappings = false; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, DisplayName="周围数据信息到TileSet索引的映射", meta=(EditCondition="bNeedOverrideMappings", EditConditionHides)) + TArray NeighborDataToIdxMappings; +}; + + +UCLASS() +class UTerrainDoubleGridDataAsset: public UDataAsset +{ + GENERATED_BODY() +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, DisplayName="瓦片设置") + TMap TerrainTileSetConfigs; +}; + + +UCLASS(BlueprintType, Blueprintable) +class UTerrainLayerComponent:public UActorComponent +{ + GENERATED_BODY(TerrainLayerComponent) + +public: + UTerrainLayerComponent(); + +public: + virtual void BeginPlay() override; + +public: + void SetTerrainData(const FGameplayTag& InTerrainType, const TArray &TerrainData); + + +protected: + void SetupTerrainMeshes(); + + +protected: + UPROPERTY(EditAnywhere, BlueprintReadOnly) + int32 MapWidth = 32; + + UPROPERTY(EditAnywhere, BlueprintReadOnly) + int32 MapHeight = 32; + + UPROPERTY(EditAnywhere, BlueprintReadOnly) + TMap> TileSetConfigs; + + UPROPERTY(EditAnywhere, BlueprintReadOnly) + TMap> TerrainMeshes; +}; diff --git a/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h b/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h index 9062237..b855c79 100644 --- a/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h +++ b/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h @@ -3,27 +3,13 @@ #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "GameplayTagContainer.h" +#include "Generator/TerrainGeneratorBase.h" +#include "Map/TerrainLayerComponent.h" #include "PaperTerrainMapActor.generated.h" -// 地形到Tile的映射结? -USTRUCT(BlueprintType) -struct FTerrainTileMapping -{ - GENERATED_BODY() - // 地形标签 - UPROPERTY(EditAnywhere, BlueprintReadWrite) - FGameplayTag TerrainTag; - // 对应的TileSet中的Tile索引 - UPROPERTY(EditAnywhere, BlueprintReadWrite) - int32 TileIndex; - FTerrainTileMapping() - : TileIndex(0) - { - } -}; UCLASS(Blueprintable, BlueprintType) class BUSYRABBIT_API APaperTerrainMapActor : public AActor @@ -38,7 +24,7 @@ protected: virtual void OnConstruction(const FTransform& Transform) override; public: - // 生成地图(可在蓝图中调用? + // 生成地图(可在蓝图中调用) UFUNCTION(BlueprintCallable, Category = "Terrain Map") void GenerateTerrainMap(); @@ -46,36 +32,31 @@ public: UFUNCTION(BlueprintCallable, Category = "Terrain Map") void ClearMap(); - // 获取地形生成器实? - UFUNCTION(BlueprintCallable, Category = "Terrain Map") - class UTerrainGenerator* GetTerrainGenerator() const; - - // 获取生成的地形数? - UFUNCTION(BlueprintCallable, Category = "Terrain Map") - TArray GetGeneratedTerrainData() const; - #if WITH_EDITOR virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; #endif -protected: - // Paper2D瓦片地图组件 - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") - class UPaperTileMapComponent* TileMapComponent; - // 地形生成器实? - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Terrain") - class UTerrainGenerator* TerrainGenerator; +protected: // 生成器相关 + // 地形生成器类 - // 生成的地形数? - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Terrain") + UPROPERTY(EditAnywhere, Category = "Terrain Map") + TSubclassOf GeneratorClass; + + // 地形生成器实例 + UPROPERTY() + TObjectPtr TerrainGenerator; + + // 生成的地形数据 TArray GeneratedTerrainData; - // 地图宽度(格子数? + +protected: // 地图配置相关 + // 地图宽度 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1", ClampMax = "1024")) int32 MapWidth = 16; - // 地图高度(格子数? + // 地图高度 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1", ClampMax = "1024")) int32 MapHeight = 16; @@ -83,35 +64,34 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1")) int32 TileSize = 16; +protected: // 瓦片相关 + TMap *TerrainTileSetConfigs; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", DisplayName="瓦片设置") + TObjectPtr TileSetDataAsset; + + +protected: + // Paper2D瓦片地图组件 + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TObjectPtr TileMapComponent; + // 是否在构造时自动生成地图 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings") - bool bAutoGenerateOnConstruction; + bool bAutoGenerateOnConstruction = true; // 是否在开始时自动生成地图 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings") - bool bAutoGenerateOnBeginPlay; - - // 地形到Tile的映射表 - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrain Mapping") - TArray TerrainTileMappings; - - // 默认Tile索引(用于未映射的地形) - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrain Mapping", meta = (ClampMin = "0")) - int32 DefaultTileIndex; - - UPROPERTY(EditAnywhere, Category = Sprite) - TObjectPtr TileSet; - + bool bAutoGenerateOnBeginPlay = true; private: // 初始化TileMap组件 void InitializeTileMap(); + void InitializeGenerator(); + // 应用地形数据到TileMap - void ApplyTerrainToTileMap(const TArray& TerrainData); + void ApplyTerrainToTileMap(const TArray& TerrainData)const; - // 根据地形标签获取Tile索引 - int32 GetTileIndexForTerrain(const FGameplayTag& TerrainTag) const; + void DrawTile(const int32 X, const int32 Y, FGameplayTag TerrainTags[4])const; - // 创建默认地形配置 - void SetupDefaultTerrainConfig(); }; diff --git a/Source/BusyRabbit/Public/Level/TerrainGenerator.h b/Source/BusyRabbit/Public/Level/TerrainGenerator.h index d1643b5..f5ef4a9 100644 --- a/Source/BusyRabbit/Public/Level/TerrainGenerator.h +++ b/Source/BusyRabbit/Public/Level/TerrainGenerator.h @@ -1,91 +1,29 @@ #pragma once #include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" #include "GameplayTagContainer.h" +#include "Generator/TerrainGeneratorBase.h" #include "TerrainGenerator.generated.h" -USTRUCT(BlueprintType) -struct FTerrainNodeInfo -{ - GENERATED_BODY() - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - FGameplayTag TerrainTag; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float Probability; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float Weight; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - int32 ParentHandle; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - TArray ChildHandles; - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - bool bIsLeafNode; - - FTerrainNodeInfo() - : Probability(1.0f) - , Weight(1.0f) - , ParentHandle(-1) - , bIsLeafNode(false) - { - } -}; UCLASS(BlueprintType, Blueprintable) -class BUSYRABBIT_API UTerrainGenerator : public UObject +class BUSYRABBIT_API UTerrainGenerator : public UTerrainGeneratorBase { GENERATED_BODY() public: UTerrainGenerator(); - UFUNCTION(BlueprintCallable, Category = "Terrain Generator") - int32 AddTerrain(const FGameplayTag& TerrainTag); - - UFUNCTION(BlueprintCallable, Category = "Terrain Generator") - void SetWeight(int32 Handle, float Weight); - - UFUNCTION(BlueprintCallable, Category = "Terrain Generator") - void SetProbability(int32 Handle, float Probability); - - UFUNCTION(BlueprintCallable, Category = "Terrain Generator") - void SetExclusive(const TArray& Handles); - - UFUNCTION(BlueprintCallable, Category = "Terrain Generator") - void BindChildTerrain(int32 ParentHandle, const TArray& ChildHandles); - - UFUNCTION(BlueprintCallable, Category = "Terrain Generator") - TArray GenerateMap(int32 Width = 256, int32 Height = 256); - - UFUNCTION(BlueprintCallable, Category = "Terrain Generator") - TMap GetAllTerrainNodes() const; + virtual bool GenerateWithNodes( + TArray& Map, int32 Width, int32 Height, + const TArray& ValidLeafNodes + )override; private: - TMap TerrainNodes; - - TArray> ExclusiveGroups; - - int32 NextHandle; - - void GetPreCheckVisibleHandles(TSet& VisibleHandles)const; - - bool ShouldNodeAppear(int32 Handle) const; - - bool CheckExclusive(int32 Handle, const TSet& AppearingNodes) const; - - TArray CalcValidLeafNodes()const; - void RegionGrowing(TArray& Map, int32 Width, int32 Height, const TArray& ValidLeafNodes) const; void ApplyPerlinNoiseSmoothing(TArray& Map, int32 Width, int32 Height) const; - FGameplayTag GetFinalTerrainTag(int32 Handle) const; }; diff --git a/Source/BusyRabbit/Public/Level/TerrainGeneratorBlueprintLibrary.h b/Source/BusyRabbit/Public/Level/TerrainGeneratorBlueprintLibrary.h index 6fb2360..95a660c 100644 --- a/Source/BusyRabbit/Public/Level/TerrainGeneratorBlueprintLibrary.h +++ b/Source/BusyRabbit/Public/Level/TerrainGeneratorBlueprintLibrary.h @@ -5,22 +5,22 @@ #include "GameplayTagContainer.h" #include "TerrainGeneratorBlueprintLibrary.generated.h" -// 蓝图函数库,方便在蓝图中使用地形生成? +// 蓝图函数库,方便在蓝图中使用地形生成�? UCLASS() class BUSYRABBIT_API UTerrainGeneratorBlueprintLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() public: - // 创建地形生成器实? + // 创建地形生成器实�? UFUNCTION(BlueprintCallable, Category = "Terrain Generator") static class UTerrainGenerator* CreateTerrainGenerator(); - // 快速设置示例地形配? + // 快速设置示例地形配�? UFUNCTION(BlueprintCallable, Category = "Terrain Generator") - static void SetupExampleTerrainConfig(class UTerrainGenerator* Generator); + static void SetupExampleTerrainConfig(class UTerrainGeneratorBase* UTerrainGenerator); - // 生成地图并返回一维数组(蓝图不支持嵌套TArray? + // 生成地图并返回一维数组(蓝图不支持嵌套TArray�? UFUNCTION(BlueprintCallable, Category = "Terrain Generator") static TArray GenerateMap( class UTerrainGenerator* Generator, @@ -36,11 +36,11 @@ public: int32& Height ); - // 获取地形标签的显示名? + // 获取地形标签的显示名�? UFUNCTION(BlueprintCallable, Category = "Terrain Generator") static FString GetTerrainDisplayName(const FGameplayTag& TerrainTag); - // 检查地形标签是否有? + // 检查地形标签是否有�? UFUNCTION(BlueprintCallable, Category = "Terrain Generator") static bool IsValidTerrainTag(const FGameplayTag& TerrainTag); }; diff --git a/Source/BusyRabbit/Public/Role/BusyRoleMovement.h b/Source/BusyRabbit/Public/Role/BusyRoleMovement.h index 02a3af1..7becf1f 100644 --- a/Source/BusyRabbit/Public/Role/BusyRoleMovement.h +++ b/Source/BusyRabbit/Public/Role/BusyRoleMovement.h @@ -1,5 +1,3 @@ -// Fill out your copyright notice in the Description page of Project Settings. - #pragma once #include "CoreMinimal.h" @@ -11,9 +9,9 @@ UENUM(BlueprintType) enum class ERoleMoveDirection: uint8 { - Move_Right, // ƶ - Move_Left, // ƶ - Move_All_Cnt // öֵ + Move_Right, + Move_Left, + Move_All_Cnt }; /** @@ -21,12 +19,14 @@ enum class ERoleMoveDirection: uint8 { */ UCLASS() class BUSYRABBIT_API UBusyRoleMovement : public ULuaActorComponent - //class BUSYRABBIT_API UBusyRoleMovement : public UObject, public ILuaOverriderInterface { GENERATED_BODY() public: UBusyRoleMovement(); + +public: + public: virtual void BeginPlay()override; @@ -37,4 +37,7 @@ public: void ReceiveComponentBeginPlay(); virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)override; + +protected: + FVector MovementDirection; };